diff --git a/.github/audit-exceptions.yml b/.github/audit-exceptions.yml
index b71422a78aef03c7ad77ae7d9ab7ed61b674c3b8..4e05aae66b079db68d499ca06eaa137381ba1aed 100644
--- a/.github/audit-exceptions.yml
+++ b/.github/audit-exceptions.yml
@@ -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"
diff --git a/.gitignore b/.gitignore
index 297c1d6f039c4a3877516e82d311da2dcb61b472..1a92ea3e641316b8bc2f88def04cee30ec1377d5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -127,6 +127,8 @@ deploy/docker-compose.override.yml
.gocache/
vite.config.js
docs/*
+!docs/PAYMENT.md
+!docs/PAYMENT_CN.md
.serena/
.codex/
frontend/coverage/
diff --git a/README.md b/README.md
index 2f73e92ae73eb776787ab9fa6926612c853d8191..3a56d089c17d75e23885afd56f6a41b843613f94 100644
--- a/README.md
+++ b/README.md
@@ -42,8 +42,9 @@ Sub2API is an AI API gateway platform designed to distribute and manage API quot
- **Smart Scheduling** - Intelligent account selection with sticky sessions
- **Concurrency Control** - Per-user and per-account concurrency limits
- **Rate Limiting** - Configurable request and token rate limits
+- **Built-in Payment System** - Supports EasyPay, Alipay, WeChat Pay, and Stripe for user self-service top-up, no separate payment service needed ([Configuration Guide](docs/PAYMENT.md))
- **Admin Dashboard** - Web interface for monitoring and management
-- **External System Integration** - Embed external systems (e.g. payment, ticketing) via iframe to extend the admin dashboard
+- **External System Integration** - Embed external systems (e.g. ticketing) via iframe to extend the admin dashboard
## ❤️ Sponsors
@@ -74,6 +75,17 @@ Sub2API is an AI API gateway platform designed to distribute and manage API quot
Thanks to SilkAPI for sponsoring this project! SilkAPI is a relay service built on Sub2API, specializing in providing high-speed and stable Codex API relay.
+
+
+
+Thanks to YLS Code for sponsoring this project! YLS Code is dedicated to building secure enterprise-grade Coding Agent productivity services, offering stable and fast Codex / Claude / Gemini subscription services along with pay-as-you-go API options for flexible choices. Register now for a limited-time 3-day Codex trial bonus!
+
+
+
+
+Thanks to AICodeMirror for sponsoring this project! AICodeMirror provides official high-stability relay services for Claude Code / Codex / Gemini CLI, with enterprise-grade concurrency, fast invoicing, and 24/7 dedicated technical support. Claude Code / Codex / Gemini official channels at 38% / 2% / 9% of original price, with extra discounts on top-ups! AICodeMirror offers special benefits for sub2api users: register via this link to enjoy 20% off your first top-up, and enterprise customers can get up to 25% off!
+
+
## Ecosystem
@@ -82,7 +94,7 @@ Community projects that extend or integrate with Sub2API:
| Project | Description | Features |
|---------|-------------|----------|
-| [Sub2ApiPay](https://github.com/touwaeriol/sub2apipay) | Self-service payment system | Self-service top-up and subscription purchase; supports YiPay protocol, WeChat Pay, Alipay, Stripe; embeddable via iframe |
+| ~~[Sub2ApiPay](https://github.com/touwaeriol/sub2apipay)~~ | ~~Self-service payment system~~ | **Now Built-in** — Payment is now integrated into Sub2API, no separate deployment needed. See [Payment Configuration Guide](docs/PAYMENT.md) |
| [sub2api-mobile](https://github.com/ckken/sub2api-mobile) | Mobile admin console | Cross-platform app (iOS/Android/Web) for user management, account management, monitoring dashboard, and multi-backend switching; built with Expo + React Native |
## Tech Stack
diff --git a/README_CN.md b/README_CN.md
index a0c3fd4bf33889274b03209f42f971951ae53d77..c0e6492e567bd2ee2fca32e8e5f9da87eab85821 100644
--- a/README_CN.md
+++ b/README_CN.md
@@ -41,8 +41,9 @@ Sub2API 是一个 AI API 网关平台,用于分发和管理 AI 产品订阅的
- **智能调度** - 智能账号选择,支持粘性会话
- **并发控制** - 用户级和账号级并发限制
- **速率限制** - 可配置的请求和 Token 速率限制
+- **内置支付系统** - 支持 EasyPay 易支付、支付宝官方、微信官方、Stripe,用户自助充值,无需独立部署支付服务([配置指南](docs/PAYMENT_CN.md))
- **管理后台** - Web 界面进行监控和管理
-- **外部系统集成** - 支持通过 iframe 嵌入外部系统(如支付、工单等),扩展管理后台功能
+- **外部系统集成** - 支持通过 iframe 嵌入外部系统(如工单等),扩展管理后台功能
## ❤️ 赞助商
@@ -73,6 +74,17 @@ Sub2API 是一个 AI API 网关平台,用于分发和管理 AI 产品订阅的
感谢 丝绸API 赞助了本项目! 丝绸API 是基于 Sub2API 搭建的中转服务,专注于提供 Codex 高速稳定API中转。
+
+
+
+感谢 伊莉思Code 赞助了本项目! 伊莉思Code 致力于构建安全的企业级Coding Agent生产力服务,提供稳定快速的 Codex / Claude / Gemini 订阅服务与即用即付API多种方案灵活选择,限时注册赠送 3 天 Codex 试用福利!
+
+
+
+
+感谢 AICodeMirror 赞助了本项目!AICodeMirror 提供 Claude Code / Codex / Gemini CLI 官方高稳定性中转服务,企业级并发、快速开票、7×24 小时专属技术支持。Claude Code / Codex / Gemini 官方通道低至原价 38% / 2% / 9%,充值更享额外折扣!AICodeMirror 为 sub2api 用户提供专属福利:通过此链接 注册,首次充值立享 8 折优惠,企业客户最高可享 75 折!
+
+
## 生态项目
@@ -81,7 +93,7 @@ Sub2API 是一个 AI API 网关平台,用于分发和管理 AI 产品订阅的
| 项目 | 说明 | 功能 |
|------|------|------|
-| [Sub2ApiPay](https://github.com/touwaeriol/sub2apipay) | 自助支付系统 | 用户自助充值、自助订阅购买;兼容易支付协议、微信官方支付、支付宝官方支付、Stripe;支持 iframe 嵌入管理后台 |
+| ~~[Sub2ApiPay](https://github.com/touwaeriol/sub2apipay)~~ | ~~自助支付系统~~ | **已内置** — 支付功能已集成到 Sub2API 中,无需独立部署。详见 [支付配置指南](docs/PAYMENT_CN.md) |
| [sub2api-mobile](https://github.com/ckken/sub2api-mobile) | 移动端管理控制台 | 跨平台应用(iOS/Android/Web),支持用户管理、账号管理、监控看板、多后端切换;基于 Expo + React Native 构建 |
## 技术栈
diff --git a/README_JA.md b/README_JA.md
index bd69e06bf5918a0aaac60b50f727ab6f6ab50169..4605b87749bd4b13031061052204794b051b88c4 100644
--- a/README_JA.md
+++ b/README_JA.md
@@ -42,8 +42,9 @@ Sub2API は、AI 製品のサブスクリプションから API クォータを
- **スマートスケジューリング** - スティッキーセッション付きのインテリジェントなアカウント選択
- **同時実行制御** - ユーザーごと・アカウントごとの同時実行数制限
- **レート制限** - 設定可能なリクエスト数およびトークンレート制限
+- **内蔵決済システム** - EasyPay、Alipay、WeChat Pay、Stripe に対応。ユーザーのセルフサービスチャージが可能で、別途決済サービスのデプロイは不要([設定ガイド](docs/PAYMENT.md))
- **管理ダッシュボード** - 監視・管理のための Web インターフェース
-- **外部システム連携** - 外部システム(決済、チケット管理など)を iframe 経由で管理ダッシュボードに埋め込み可能
+- **外部システム連携** - 外部システム(チケット管理など)を iframe 経由で管理ダッシュボードに埋め込み可能
## ❤️ スポンサー
@@ -73,6 +74,17 @@ Sub2API は、AI 製品のサブスクリプションから API クォータを
SilkAPI のご支援に感謝します!SilkAPI は Sub2API をベースに構築された中継サービスで、高速かつ安定した Codex API 中継の提供に特化しています。
+
+
+
+YLS Code のご支援に感謝します!YLS Code は安全なエンタープライズグレードの Coding Agent 生産性サービスの構築に取り組んでおり、安定かつ高速な Codex / Claude / Gemini サブスクリプションサービスと従量課金 API の柔軟なプランを提供しています。期間限定で新規登録者に 3 日間の Codex 試用特典をプレゼント中!
+
+
+
+
+AICodeMirror のご支援に感謝します!AICodeMirror は Claude Code / Codex / Gemini CLI の公式高安定性リレーサービスを提供しており、エンタープライズグレードの同時実行、迅速な請求書発行、24時間年中無休の専属テクニカルサポートを備えています。Claude Code / Codex / Gemini の公式チャネルを定価の 38% / 2% / 9% で利用可能、チャージ時にはさらに追加割引!AICodeMirror は sub2api ユーザー向けに特別特典を提供中:こちらのリンク から登録すると、初回チャージが 20% オフ、法人のお客様は最大 25% オフ!
+
+
## エコシステム
@@ -81,7 +93,7 @@ Sub2API を拡張・統合するコミュニティプロジェクト:
| プロジェクト | 説明 | 機能 |
|---------|-------------|----------|
-| [Sub2ApiPay](https://github.com/touwaeriol/sub2apipay) | セルフサービス決済システム | セルフサービスによるチャージおよびサブスクリプション購入。YiPay プロトコル、WeChat Pay、Alipay、Stripe 対応。iframe での埋め込み可能 |
+| ~~[Sub2ApiPay](https://github.com/touwaeriol/sub2apipay)~~ | ~~セルフサービス決済システム~~ | **内蔵済み** — 決済機能は Sub2API に統合されました。別途デプロイは不要です。[決済設定ガイド](docs/PAYMENT.md)をご参照ください |
| [sub2api-mobile](https://github.com/ckken/sub2api-mobile) | モバイル管理コンソール | ユーザー管理、アカウント管理、監視ダッシュボード、マルチバックエンド切り替えが可能なクロスプラットフォームアプリ(iOS/Android/Web)。Expo + React Native で構築 |
## 技術スタック
diff --git a/assets/partners/logos/AICodeMirror.jpg b/assets/partners/logos/AICodeMirror.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..1c98b2238a28ab2ffaaa84a9aa6b9b4094cf03f9
Binary files /dev/null and b/assets/partners/logos/AICodeMirror.jpg differ
diff --git a/assets/partners/logos/ylscode.png b/assets/partners/logos/ylscode.png
new file mode 100644
index 0000000000000000000000000000000000000000..4d374f04c2ffea503d71e0022b2937000d206126
Binary files /dev/null and b/assets/partners/logos/ylscode.png differ
diff --git a/backend/cmd/server/VERSION b/backend/cmd/server/VERSION
index b6e5c2adfabb39b565076ddb6d77cdb9bfad8e3a..715965f3592988297893c9a86c43b3e55633f3bd 100644
--- a/backend/cmd/server/VERSION
+++ b/backend/cmd/server/VERSION
@@ -1 +1 @@
-0.1.110
+0.1.111
diff --git a/backend/cmd/server/wire.go b/backend/cmd/server/wire.go
index 7fc648ac3a6955aca76468b99b9146a9ccd2b20c..47f8f518ff19ec29d79477ee984efc948da05269 100644
--- a/backend/cmd/server/wire.go
+++ b/backend/cmd/server/wire.go
@@ -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{
diff --git a/backend/cmd/server/wire_gen.go b/backend/cmd/server/wire_gen.go
index fdc5c6acdb792e38e2d4400c272ce6a6fa2b0903..c288a289169515ab329cbac86e6943593ba60232 100644
--- a/backend/cmd/server/wire_gen.go
+++ b/backend/cmd/server/wire_gen.go
@@ -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
@@ -100,7 +110,7 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
}
dashboardAggregationService := service.ProvideDashboardAggregationService(dashboardAggregationRepository, timingWheelService, configConfig)
dashboardHandler := admin.NewDashboardHandler(dashboardService, dashboardAggregationService)
- schedulerCache := repository.NewSchedulerCache(redisClient)
+ schedulerCache := repository.ProvideSchedulerCache(redisClient, configConfig)
accountRepository := repository.NewAccountRepository(client, db, schedulerCache)
proxyRepository := repository.NewProxyRepository(client, db)
proxyExitInfoProber := repository.NewProxyExitInfoProber(configConfig)
@@ -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{
diff --git a/backend/cmd/server/wire_gen_test.go b/backend/cmd/server/wire_gen_test.go
index 6e4561c9943373846cd9512ed8901aafcc1d0328..a6e0551a382e35e462c73373899f5f713da6e962 100644
--- a/backend/cmd/server/wire_gen_test.go
+++ b/backend/cmd/server/wire_gen_test.go
@@ -75,6 +75,7 @@ func TestProvideCleanup_WithMinimalDependencies_NoPanic(t *testing.T) {
nil, // openAIGateway
nil, // scheduledTestRunner
nil, // backupSvc
+ nil, // paymentOrderExpiry
)
require.NotPanics(t, func() {
diff --git a/backend/ent/client.go b/backend/ent/client.go
index 4129d6c5fefddc17f5b97de9fc68506dd673902a..e52e015ad8b1fe576a57f6298562a5619ab3e4a8 100644
--- a/backend/ent/client.go
+++ b/backend/ent/client.go
@@ -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
}
diff --git a/backend/ent/ent.go b/backend/ent/ent.go
index bdeaed8a8ed40f3b7bfae1459c20105ef3f885ba..96ed5e03a9569a3b5a33922573aa9a2fbf090825 100644
--- a/backend/ent/ent.go
+++ b/backend/ent/ent.go
@@ -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,
diff --git a/backend/ent/group.go b/backend/ent/group.go
index b15ac15dd923161a5880ed6132ea226ab9a5b909..f10b50c325e5b7cc507df510b9c222737aa1bc9c 100644
--- a/backend/ent/group.go
+++ b/backend/ent/group.go
@@ -11,6 +11,7 @@ import (
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"github.com/Wei-Shaw/sub2api/ent/group"
+ "github.com/Wei-Shaw/sub2api/internal/domain"
)
// Group is the model entity for the Group schema.
@@ -76,6 +77,8 @@ type Group struct {
RequirePrivacySet bool `json:"require_privacy_set,omitempty"`
// 默认映射模型 ID,当账号级映射找不到时使用此值
DefaultMappedModel string `json:"default_mapped_model,omitempty"`
+ // OpenAI Messages 调度模型配置:按 Claude 系列/精确模型映射到目标 GPT 模型
+ MessagesDispatchModelConfig domain.OpenAIMessagesDispatchModelConfig `json:"messages_dispatch_model_config,omitempty"`
// Edges holds the relations/edges for other nodes in the graph.
// The values are being populated by the GroupQuery when eager-loading is set.
Edges GroupEdges `json:"edges"`
@@ -182,7 +185,7 @@ func (*Group) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
for i := range columns {
switch columns[i] {
- case group.FieldModelRouting, group.FieldSupportedModelScopes:
+ case group.FieldModelRouting, group.FieldSupportedModelScopes, group.FieldMessagesDispatchModelConfig:
values[i] = new([]byte)
case group.FieldIsExclusive, group.FieldClaudeCodeOnly, group.FieldModelRoutingEnabled, group.FieldMcpXMLInject, group.FieldAllowMessagesDispatch, group.FieldRequireOauthOnly, group.FieldRequirePrivacySet:
values[i] = new(sql.NullBool)
@@ -403,6 +406,14 @@ func (_m *Group) assignValues(columns []string, values []any) error {
} else if value.Valid {
_m.DefaultMappedModel = value.String
}
+ case group.FieldMessagesDispatchModelConfig:
+ if value, ok := values[i].(*[]byte); !ok {
+ return fmt.Errorf("unexpected type %T for field messages_dispatch_model_config", values[i])
+ } else if value != nil && len(*value) > 0 {
+ if err := json.Unmarshal(*value, &_m.MessagesDispatchModelConfig); err != nil {
+ return fmt.Errorf("unmarshal field messages_dispatch_model_config: %w", err)
+ }
+ }
default:
_m.selectValues.Set(columns[i], values[i])
}
@@ -585,6 +596,9 @@ func (_m *Group) String() string {
builder.WriteString(", ")
builder.WriteString("default_mapped_model=")
builder.WriteString(_m.DefaultMappedModel)
+ builder.WriteString(", ")
+ builder.WriteString("messages_dispatch_model_config=")
+ builder.WriteString(fmt.Sprintf("%v", _m.MessagesDispatchModelConfig))
builder.WriteByte(')')
return builder.String()
}
diff --git a/backend/ent/group/group.go b/backend/ent/group/group.go
index 21a7c2cb767d9cf5be157ecc93a51881126f76d5..b1371630dd3caf5ebb806f99dd62e1bd004612b6 100644
--- a/backend/ent/group/group.go
+++ b/backend/ent/group/group.go
@@ -8,6 +8,7 @@ import (
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
+ "github.com/Wei-Shaw/sub2api/internal/domain"
)
const (
@@ -73,6 +74,8 @@ const (
FieldRequirePrivacySet = "require_privacy_set"
// FieldDefaultMappedModel holds the string denoting the default_mapped_model field in the database.
FieldDefaultMappedModel = "default_mapped_model"
+ // FieldMessagesDispatchModelConfig holds the string denoting the messages_dispatch_model_config field in the database.
+ FieldMessagesDispatchModelConfig = "messages_dispatch_model_config"
// EdgeAPIKeys holds the string denoting the api_keys edge name in mutations.
EdgeAPIKeys = "api_keys"
// EdgeRedeemCodes holds the string denoting the redeem_codes edge name in mutations.
@@ -177,6 +180,7 @@ var Columns = []string{
FieldRequireOauthOnly,
FieldRequirePrivacySet,
FieldDefaultMappedModel,
+ FieldMessagesDispatchModelConfig,
}
var (
@@ -252,6 +256,8 @@ var (
DefaultDefaultMappedModel string
// DefaultMappedModelValidator is a validator for the "default_mapped_model" field. It is called by the builders before save.
DefaultMappedModelValidator func(string) error
+ // DefaultMessagesDispatchModelConfig holds the default value on creation for the "messages_dispatch_model_config" field.
+ DefaultMessagesDispatchModelConfig domain.OpenAIMessagesDispatchModelConfig
)
// OrderOption defines the ordering options for the Group queries.
diff --git a/backend/ent/group_create.go b/backend/ent/group_create.go
index a8c30b184ddc04336783e7f086ee0b24a6df21f9..f412fa4070c13875f006c492903e9f7f80ecb62e 100644
--- a/backend/ent/group_create.go
+++ b/backend/ent/group_create.go
@@ -18,6 +18,7 @@ import (
"github.com/Wei-Shaw/sub2api/ent/usagelog"
"github.com/Wei-Shaw/sub2api/ent/user"
"github.com/Wei-Shaw/sub2api/ent/usersubscription"
+ "github.com/Wei-Shaw/sub2api/internal/domain"
)
// GroupCreate is the builder for creating a Group entity.
@@ -410,6 +411,20 @@ func (_c *GroupCreate) SetNillableDefaultMappedModel(v *string) *GroupCreate {
return _c
}
+// SetMessagesDispatchModelConfig sets the "messages_dispatch_model_config" field.
+func (_c *GroupCreate) SetMessagesDispatchModelConfig(v domain.OpenAIMessagesDispatchModelConfig) *GroupCreate {
+ _c.mutation.SetMessagesDispatchModelConfig(v)
+ return _c
+}
+
+// SetNillableMessagesDispatchModelConfig sets the "messages_dispatch_model_config" field if the given value is not nil.
+func (_c *GroupCreate) SetNillableMessagesDispatchModelConfig(v *domain.OpenAIMessagesDispatchModelConfig) *GroupCreate {
+ if v != nil {
+ _c.SetMessagesDispatchModelConfig(*v)
+ }
+ return _c
+}
+
// AddAPIKeyIDs adds the "api_keys" edge to the APIKey entity by IDs.
func (_c *GroupCreate) AddAPIKeyIDs(ids ...int64) *GroupCreate {
_c.mutation.AddAPIKeyIDs(ids...)
@@ -611,6 +626,10 @@ func (_c *GroupCreate) defaults() error {
v := group.DefaultDefaultMappedModel
_c.mutation.SetDefaultMappedModel(v)
}
+ if _, ok := _c.mutation.MessagesDispatchModelConfig(); !ok {
+ v := group.DefaultMessagesDispatchModelConfig
+ _c.mutation.SetMessagesDispatchModelConfig(v)
+ }
return nil
}
@@ -695,6 +714,9 @@ func (_c *GroupCreate) check() error {
return &ValidationError{Name: "default_mapped_model", err: fmt.Errorf(`ent: validator failed for field "Group.default_mapped_model": %w`, err)}
}
}
+ if _, ok := _c.mutation.MessagesDispatchModelConfig(); !ok {
+ return &ValidationError{Name: "messages_dispatch_model_config", err: errors.New(`ent: missing required field "Group.messages_dispatch_model_config"`)}
+ }
return nil
}
@@ -838,6 +860,10 @@ func (_c *GroupCreate) createSpec() (*Group, *sqlgraph.CreateSpec) {
_spec.SetField(group.FieldDefaultMappedModel, field.TypeString, value)
_node.DefaultMappedModel = value
}
+ if value, ok := _c.mutation.MessagesDispatchModelConfig(); ok {
+ _spec.SetField(group.FieldMessagesDispatchModelConfig, field.TypeJSON, value)
+ _node.MessagesDispatchModelConfig = value
+ }
if nodes := _c.mutation.APIKeysIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
@@ -1462,6 +1488,18 @@ func (u *GroupUpsert) UpdateDefaultMappedModel() *GroupUpsert {
return u
}
+// SetMessagesDispatchModelConfig sets the "messages_dispatch_model_config" field.
+func (u *GroupUpsert) SetMessagesDispatchModelConfig(v domain.OpenAIMessagesDispatchModelConfig) *GroupUpsert {
+ u.Set(group.FieldMessagesDispatchModelConfig, v)
+ return u
+}
+
+// UpdateMessagesDispatchModelConfig sets the "messages_dispatch_model_config" field to the value that was provided on create.
+func (u *GroupUpsert) UpdateMessagesDispatchModelConfig() *GroupUpsert {
+ u.SetExcluded(group.FieldMessagesDispatchModelConfig)
+ return u
+}
+
// UpdateNewValues updates the mutable fields using the new values that were set on create.
// Using this option is equivalent to using:
//
@@ -2053,6 +2091,20 @@ func (u *GroupUpsertOne) UpdateDefaultMappedModel() *GroupUpsertOne {
})
}
+// SetMessagesDispatchModelConfig sets the "messages_dispatch_model_config" field.
+func (u *GroupUpsertOne) SetMessagesDispatchModelConfig(v domain.OpenAIMessagesDispatchModelConfig) *GroupUpsertOne {
+ return u.Update(func(s *GroupUpsert) {
+ s.SetMessagesDispatchModelConfig(v)
+ })
+}
+
+// UpdateMessagesDispatchModelConfig sets the "messages_dispatch_model_config" field to the value that was provided on create.
+func (u *GroupUpsertOne) UpdateMessagesDispatchModelConfig() *GroupUpsertOne {
+ return u.Update(func(s *GroupUpsert) {
+ s.UpdateMessagesDispatchModelConfig()
+ })
+}
+
// Exec executes the query.
func (u *GroupUpsertOne) Exec(ctx context.Context) error {
if len(u.create.conflict) == 0 {
@@ -2810,6 +2862,20 @@ func (u *GroupUpsertBulk) UpdateDefaultMappedModel() *GroupUpsertBulk {
})
}
+// SetMessagesDispatchModelConfig sets the "messages_dispatch_model_config" field.
+func (u *GroupUpsertBulk) SetMessagesDispatchModelConfig(v domain.OpenAIMessagesDispatchModelConfig) *GroupUpsertBulk {
+ return u.Update(func(s *GroupUpsert) {
+ s.SetMessagesDispatchModelConfig(v)
+ })
+}
+
+// UpdateMessagesDispatchModelConfig sets the "messages_dispatch_model_config" field to the value that was provided on create.
+func (u *GroupUpsertBulk) UpdateMessagesDispatchModelConfig() *GroupUpsertBulk {
+ return u.Update(func(s *GroupUpsert) {
+ s.UpdateMessagesDispatchModelConfig()
+ })
+}
+
// Exec executes the query.
func (u *GroupUpsertBulk) Exec(ctx context.Context) error {
if u.create.err != nil {
diff --git a/backend/ent/group_update.go b/backend/ent/group_update.go
index aa1a83d421eb7f897a55a6e9de8d0e9ba5174ba8..7b6d6193256a1baf00559edc0f2381bb1ba26a3a 100644
--- a/backend/ent/group_update.go
+++ b/backend/ent/group_update.go
@@ -20,6 +20,7 @@ import (
"github.com/Wei-Shaw/sub2api/ent/usagelog"
"github.com/Wei-Shaw/sub2api/ent/user"
"github.com/Wei-Shaw/sub2api/ent/usersubscription"
+ "github.com/Wei-Shaw/sub2api/internal/domain"
)
// GroupUpdate is the builder for updating Group entities.
@@ -552,6 +553,20 @@ func (_u *GroupUpdate) SetNillableDefaultMappedModel(v *string) *GroupUpdate {
return _u
}
+// SetMessagesDispatchModelConfig sets the "messages_dispatch_model_config" field.
+func (_u *GroupUpdate) SetMessagesDispatchModelConfig(v domain.OpenAIMessagesDispatchModelConfig) *GroupUpdate {
+ _u.mutation.SetMessagesDispatchModelConfig(v)
+ return _u
+}
+
+// SetNillableMessagesDispatchModelConfig sets the "messages_dispatch_model_config" field if the given value is not nil.
+func (_u *GroupUpdate) SetNillableMessagesDispatchModelConfig(v *domain.OpenAIMessagesDispatchModelConfig) *GroupUpdate {
+ if v != nil {
+ _u.SetMessagesDispatchModelConfig(*v)
+ }
+ return _u
+}
+
// AddAPIKeyIDs adds the "api_keys" edge to the APIKey entity by IDs.
func (_u *GroupUpdate) AddAPIKeyIDs(ids ...int64) *GroupUpdate {
_u.mutation.AddAPIKeyIDs(ids...)
@@ -1012,6 +1027,9 @@ func (_u *GroupUpdate) sqlSave(ctx context.Context) (_node int, err error) {
if value, ok := _u.mutation.DefaultMappedModel(); ok {
_spec.SetField(group.FieldDefaultMappedModel, field.TypeString, value)
}
+ if value, ok := _u.mutation.MessagesDispatchModelConfig(); ok {
+ _spec.SetField(group.FieldMessagesDispatchModelConfig, field.TypeJSON, value)
+ }
if _u.mutation.APIKeysCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
@@ -1843,6 +1861,20 @@ func (_u *GroupUpdateOne) SetNillableDefaultMappedModel(v *string) *GroupUpdateO
return _u
}
+// SetMessagesDispatchModelConfig sets the "messages_dispatch_model_config" field.
+func (_u *GroupUpdateOne) SetMessagesDispatchModelConfig(v domain.OpenAIMessagesDispatchModelConfig) *GroupUpdateOne {
+ _u.mutation.SetMessagesDispatchModelConfig(v)
+ return _u
+}
+
+// SetNillableMessagesDispatchModelConfig sets the "messages_dispatch_model_config" field if the given value is not nil.
+func (_u *GroupUpdateOne) SetNillableMessagesDispatchModelConfig(v *domain.OpenAIMessagesDispatchModelConfig) *GroupUpdateOne {
+ if v != nil {
+ _u.SetMessagesDispatchModelConfig(*v)
+ }
+ return _u
+}
+
// AddAPIKeyIDs adds the "api_keys" edge to the APIKey entity by IDs.
func (_u *GroupUpdateOne) AddAPIKeyIDs(ids ...int64) *GroupUpdateOne {
_u.mutation.AddAPIKeyIDs(ids...)
@@ -2333,6 +2365,9 @@ func (_u *GroupUpdateOne) sqlSave(ctx context.Context) (_node *Group, err error)
if value, ok := _u.mutation.DefaultMappedModel(); ok {
_spec.SetField(group.FieldDefaultMappedModel, field.TypeString, value)
}
+ if value, ok := _u.mutation.MessagesDispatchModelConfig(); ok {
+ _spec.SetField(group.FieldMessagesDispatchModelConfig, field.TypeJSON, value)
+ }
if _u.mutation.APIKeysCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
diff --git a/backend/ent/hook/hook.go b/backend/ent/hook/hook.go
index f6f7b4e9b4ff5cb40863c049458a60b9c2f3a8d8..199dacea0ecc8c8a06ac0e0789e5e968ec63b66e 100644
--- a/backend/ent/hook/hook.go
+++ b/backend/ent/hook/hook.go
@@ -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)
diff --git a/backend/ent/intercept/intercept.go b/backend/ent/intercept/intercept.go
index 13169ca7151704a2873806fff570ca1c19d8f5c8..8d8320bbba5f6cdec4ba26ce89e5a81f532d30f9 100644
--- a/backend/ent/intercept/intercept.go
+++ b/backend/ent/intercept/intercept.go
@@ -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:
diff --git a/backend/ent/migrate/schema.go b/backend/ent/migrate/schema.go
index 5400bf9319c0adf1233cdbd7c3edad36d7b743e6..e947b2e88253cd79eb6f9803d9a1b8b96ac2f63e 100644
--- a/backend/ent/migrate/schema.go
+++ b/backend/ent/migrate/schema.go
@@ -407,6 +407,7 @@ var (
{Name: "require_oauth_only", Type: field.TypeBool, Default: false},
{Name: "require_privacy_set", Type: field.TypeBool, Default: false},
{Name: "default_mapped_model", Type: field.TypeString, Size: 100, Default: ""},
+ {Name: "messages_dispatch_model_config", Type: field.TypeJSON, SchemaType: map[string]string{"postgres": "jsonb"}},
}
// GroupsTable holds the schema information for the "groups" table.
GroupsTable = &schema.Table{
@@ -484,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},
@@ -670,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},
@@ -1127,12 +1315,16 @@ var (
ErrorPassthroughRulesTable,
GroupsTable,
IdempotencyRecordsTable,
+ PaymentAuditLogsTable,
+ PaymentOrdersTable,
+ PaymentProviderInstancesTable,
PromoCodesTable,
PromoCodeUsagesTable,
ProxiesTable,
RedeemCodesTable,
SecuritySecretsTable,
SettingsTable,
+ SubscriptionPlansTable,
TLSFingerprintProfilesTable,
UsageCleanupTasksTable,
UsageLogsTable,
@@ -1176,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",
}
@@ -1198,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",
}
diff --git a/backend/ent/mutation.go b/backend/ent/mutation.go
index d206039af4714f5542a1001efc5867bcf806c58e..6b2fa838692468cba8ea65b624f57192665c2525 100644
--- a/backend/ent/mutation.go
+++ b/backend/ent/mutation.go
@@ -20,6 +20,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"
@@ -27,6 +30,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"
@@ -55,12 +59,16 @@ const (
TypeErrorPassthroughRule = "ErrorPassthroughRule"
TypeGroup = "Group"
TypeIdempotencyRecord = "IdempotencyRecord"
+ TypePaymentAuditLog = "PaymentAuditLog"
+ TypePaymentOrder = "PaymentOrder"
+ TypePaymentProviderInstance = "PaymentProviderInstance"
TypePromoCode = "PromoCode"
TypePromoCodeUsage = "PromoCodeUsage"
TypeProxy = "Proxy"
TypeRedeemCode = "RedeemCode"
TypeSecuritySecret = "SecuritySecret"
TypeSetting = "Setting"
+ TypeSubscriptionPlan = "SubscriptionPlan"
TypeTLSFingerprintProfile = "TLSFingerprintProfile"
TypeUsageCleanupTask = "UsageCleanupTask"
TypeUsageLog = "UsageLog"
@@ -8246,6 +8254,7 @@ type GroupMutation struct {
require_oauth_only *bool
require_privacy_set *bool
default_mapped_model *string
+ messages_dispatch_model_config *domain.OpenAIMessagesDispatchModelConfig
clearedFields map[string]struct{}
api_keys map[int64]struct{}
removedapi_keys map[int64]struct{}
@@ -9798,6 +9807,42 @@ func (m *GroupMutation) ResetDefaultMappedModel() {
m.default_mapped_model = nil
}
+// SetMessagesDispatchModelConfig sets the "messages_dispatch_model_config" field.
+func (m *GroupMutation) SetMessagesDispatchModelConfig(damdmc domain.OpenAIMessagesDispatchModelConfig) {
+ m.messages_dispatch_model_config = &damdmc
+}
+
+// MessagesDispatchModelConfig returns the value of the "messages_dispatch_model_config" field in the mutation.
+func (m *GroupMutation) MessagesDispatchModelConfig() (r domain.OpenAIMessagesDispatchModelConfig, exists bool) {
+ v := m.messages_dispatch_model_config
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldMessagesDispatchModelConfig returns the old "messages_dispatch_model_config" field's value of the Group entity.
+// If the Group object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *GroupMutation) OldMessagesDispatchModelConfig(ctx context.Context) (v domain.OpenAIMessagesDispatchModelConfig, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldMessagesDispatchModelConfig is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldMessagesDispatchModelConfig requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldMessagesDispatchModelConfig: %w", err)
+ }
+ return oldValue.MessagesDispatchModelConfig, nil
+}
+
+// ResetMessagesDispatchModelConfig resets all changes to the "messages_dispatch_model_config" field.
+func (m *GroupMutation) ResetMessagesDispatchModelConfig() {
+ m.messages_dispatch_model_config = nil
+}
+
// AddAPIKeyIDs adds the "api_keys" edge to the APIKey entity by ids.
func (m *GroupMutation) AddAPIKeyIDs(ids ...int64) {
if m.api_keys == nil {
@@ -10156,7 +10201,7 @@ func (m *GroupMutation) Type() string {
// order to get all numeric fields that were incremented/decremented, call
// AddedFields().
func (m *GroupMutation) Fields() []string {
- fields := make([]string, 0, 29)
+ fields := make([]string, 0, 30)
if m.created_at != nil {
fields = append(fields, group.FieldCreatedAt)
}
@@ -10244,6 +10289,9 @@ func (m *GroupMutation) Fields() []string {
if m.default_mapped_model != nil {
fields = append(fields, group.FieldDefaultMappedModel)
}
+ if m.messages_dispatch_model_config != nil {
+ fields = append(fields, group.FieldMessagesDispatchModelConfig)
+ }
return fields
}
@@ -10310,6 +10358,8 @@ func (m *GroupMutation) Field(name string) (ent.Value, bool) {
return m.RequirePrivacySet()
case group.FieldDefaultMappedModel:
return m.DefaultMappedModel()
+ case group.FieldMessagesDispatchModelConfig:
+ return m.MessagesDispatchModelConfig()
}
return nil, false
}
@@ -10377,6 +10427,8 @@ func (m *GroupMutation) OldField(ctx context.Context, name string) (ent.Value, e
return m.OldRequirePrivacySet(ctx)
case group.FieldDefaultMappedModel:
return m.OldDefaultMappedModel(ctx)
+ case group.FieldMessagesDispatchModelConfig:
+ return m.OldMessagesDispatchModelConfig(ctx)
}
return nil, fmt.Errorf("unknown Group field %s", name)
}
@@ -10589,6 +10641,13 @@ func (m *GroupMutation) SetField(name string, value ent.Value) error {
}
m.SetDefaultMappedModel(v)
return nil
+ case group.FieldMessagesDispatchModelConfig:
+ v, ok := value.(domain.OpenAIMessagesDispatchModelConfig)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetMessagesDispatchModelConfig(v)
+ return nil
}
return fmt.Errorf("unknown Group field %s", name)
}
@@ -10929,6 +10988,9 @@ func (m *GroupMutation) ResetField(name string) error {
case group.FieldDefaultMappedModel:
m.ResetDefaultMappedModel()
return nil
+ case group.FieldMessagesDispatchModelConfig:
+ m.ResetMessagesDispatchModelConfig()
+ return nil
}
return fmt.Errorf("unknown Group field %s", name)
}
@@ -12129,44 +12191,34 @@ func (m *IdempotencyRecordMutation) ResetEdge(name string) error {
return fmt.Errorf("unknown IdempotencyRecord edge %s", name)
}
-// PromoCodeMutation represents an operation that mutates the PromoCode nodes in the graph.
-type PromoCodeMutation struct {
+// PaymentAuditLogMutation represents an operation that mutates the PaymentAuditLog nodes in the graph.
+type PaymentAuditLogMutation struct {
config
- op Op
- typ string
- id *int64
- code *string
- bonus_amount *float64
- addbonus_amount *float64
- max_uses *int
- addmax_uses *int
- used_count *int
- addused_count *int
- status *string
- expires_at *time.Time
- notes *string
- created_at *time.Time
- updated_at *time.Time
- clearedFields map[string]struct{}
- usage_records map[int64]struct{}
- removedusage_records map[int64]struct{}
- clearedusage_records bool
- done bool
- oldValue func(context.Context) (*PromoCode, error)
- predicates []predicate.PromoCode
+ op Op
+ typ string
+ id *int64
+ order_id *string
+ action *string
+ detail *string
+ operator *string
+ created_at *time.Time
+ clearedFields map[string]struct{}
+ done bool
+ oldValue func(context.Context) (*PaymentAuditLog, error)
+ predicates []predicate.PaymentAuditLog
}
-var _ ent.Mutation = (*PromoCodeMutation)(nil)
+var _ ent.Mutation = (*PaymentAuditLogMutation)(nil)
-// promocodeOption allows management of the mutation configuration using functional options.
-type promocodeOption func(*PromoCodeMutation)
+// paymentauditlogOption allows management of the mutation configuration using functional options.
+type paymentauditlogOption func(*PaymentAuditLogMutation)
-// newPromoCodeMutation creates new mutation for the PromoCode entity.
-func newPromoCodeMutation(c config, op Op, opts ...promocodeOption) *PromoCodeMutation {
- m := &PromoCodeMutation{
+// newPaymentAuditLogMutation creates new mutation for the PaymentAuditLog entity.
+func newPaymentAuditLogMutation(c config, op Op, opts ...paymentauditlogOption) *PaymentAuditLogMutation {
+ m := &PaymentAuditLogMutation{
config: c,
op: op,
- typ: TypePromoCode,
+ typ: TypePaymentAuditLog,
clearedFields: make(map[string]struct{}),
}
for _, opt := range opts {
@@ -12175,20 +12227,20 @@ func newPromoCodeMutation(c config, op Op, opts ...promocodeOption) *PromoCodeMu
return m
}
-// withPromoCodeID sets the ID field of the mutation.
-func withPromoCodeID(id int64) promocodeOption {
- return func(m *PromoCodeMutation) {
+// withPaymentAuditLogID sets the ID field of the mutation.
+func withPaymentAuditLogID(id int64) paymentauditlogOption {
+ return func(m *PaymentAuditLogMutation) {
var (
err error
once sync.Once
- value *PromoCode
+ value *PaymentAuditLog
)
- m.oldValue = func(ctx context.Context) (*PromoCode, error) {
+ m.oldValue = func(ctx context.Context) (*PaymentAuditLog, error) {
once.Do(func() {
if m.done {
err = errors.New("querying old values post mutation is not allowed")
} else {
- value, err = m.Client().PromoCode.Get(ctx, id)
+ value, err = m.Client().PaymentAuditLog.Get(ctx, id)
}
})
return value, err
@@ -12197,10 +12249,10 @@ func withPromoCodeID(id int64) promocodeOption {
}
}
-// withPromoCode sets the old PromoCode of the mutation.
-func withPromoCode(node *PromoCode) promocodeOption {
- return func(m *PromoCodeMutation) {
- m.oldValue = func(context.Context) (*PromoCode, error) {
+// withPaymentAuditLog sets the old PaymentAuditLog of the mutation.
+func withPaymentAuditLog(node *PaymentAuditLog) paymentauditlogOption {
+ return func(m *PaymentAuditLogMutation) {
+ m.oldValue = func(context.Context) (*PaymentAuditLog, error) {
return node, nil
}
m.id = &node.ID
@@ -12209,7 +12261,7 @@ func withPromoCode(node *PromoCode) promocodeOption {
// Client returns a new `ent.Client` from the mutation. If the mutation was
// executed in a transaction (ent.Tx), a transactional client is returned.
-func (m PromoCodeMutation) Client() *Client {
+func (m PaymentAuditLogMutation) Client() *Client {
client := &Client{config: m.config}
client.init()
return client
@@ -12217,7 +12269,7 @@ func (m PromoCodeMutation) Client() *Client {
// Tx returns an `ent.Tx` for mutations that were executed in transactions;
// it returns an error otherwise.
-func (m PromoCodeMutation) Tx() (*Tx, error) {
+func (m PaymentAuditLogMutation) Tx() (*Tx, error) {
if _, ok := m.driver.(*txDriver); !ok {
return nil, errors.New("ent: mutation is not running in a transaction")
}
@@ -12228,7 +12280,7 @@ func (m PromoCodeMutation) Tx() (*Tx, error) {
// ID returns the ID value in the mutation. Note that the ID is only available
// if it was provided to the builder or after it was returned from the database.
-func (m *PromoCodeMutation) ID() (id int64, exists bool) {
+func (m *PaymentAuditLogMutation) ID() (id int64, exists bool) {
if m.id == nil {
return
}
@@ -12239,7 +12291,7 @@ func (m *PromoCodeMutation) ID() (id int64, exists bool) {
// That means, if the mutation is applied within a transaction with an isolation level such
// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated
// or updated by the mutation.
-func (m *PromoCodeMutation) IDs(ctx context.Context) ([]int64, error) {
+func (m *PaymentAuditLogMutation) IDs(ctx context.Context) ([]int64, error) {
switch {
case m.op.Is(OpUpdateOne | OpDeleteOne):
id, exists := m.ID()
@@ -12248,1225 +12300,6471 @@ func (m *PromoCodeMutation) IDs(ctx context.Context) ([]int64, error) {
}
fallthrough
case m.op.Is(OpUpdate | OpDelete):
- return m.Client().PromoCode.Query().Where(m.predicates...).IDs(ctx)
+ return m.Client().PaymentAuditLog.Query().Where(m.predicates...).IDs(ctx)
default:
return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op)
}
}
-// SetCode sets the "code" field.
-func (m *PromoCodeMutation) SetCode(s string) {
- m.code = &s
+// SetOrderID sets the "order_id" field.
+func (m *PaymentAuditLogMutation) SetOrderID(s string) {
+ m.order_id = &s
}
-// Code returns the value of the "code" field in the mutation.
-func (m *PromoCodeMutation) Code() (r string, exists bool) {
- v := m.code
+// OrderID returns the value of the "order_id" field in the mutation.
+func (m *PaymentAuditLogMutation) OrderID() (r string, exists bool) {
+ v := m.order_id
if v == nil {
return
}
return *v, true
}
-// OldCode returns the old "code" field's value of the PromoCode entity.
-// If the PromoCode object wasn't provided to the builder, the object is fetched from the database.
+// OldOrderID returns the old "order_id" field's value of the PaymentAuditLog entity.
+// If the PaymentAuditLog object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
-func (m *PromoCodeMutation) OldCode(ctx context.Context) (v string, err error) {
+func (m *PaymentAuditLogMutation) OldOrderID(ctx context.Context) (v string, err error) {
if !m.op.Is(OpUpdateOne) {
- return v, errors.New("OldCode is only allowed on UpdateOne operations")
+ return v, errors.New("OldOrderID is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
- return v, errors.New("OldCode requires an ID field in the mutation")
+ return v, errors.New("OldOrderID requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
- return v, fmt.Errorf("querying old value for OldCode: %w", err)
+ return v, fmt.Errorf("querying old value for OldOrderID: %w", err)
}
- return oldValue.Code, nil
+ return oldValue.OrderID, nil
}
-// ResetCode resets all changes to the "code" field.
-func (m *PromoCodeMutation) ResetCode() {
- m.code = nil
+// ResetOrderID resets all changes to the "order_id" field.
+func (m *PaymentAuditLogMutation) ResetOrderID() {
+ m.order_id = nil
}
-// SetBonusAmount sets the "bonus_amount" field.
-func (m *PromoCodeMutation) SetBonusAmount(f float64) {
- m.bonus_amount = &f
- m.addbonus_amount = nil
+// SetAction sets the "action" field.
+func (m *PaymentAuditLogMutation) SetAction(s string) {
+ m.action = &s
}
-// BonusAmount returns the value of the "bonus_amount" field in the mutation.
-func (m *PromoCodeMutation) BonusAmount() (r float64, exists bool) {
- v := m.bonus_amount
+// Action returns the value of the "action" field in the mutation.
+func (m *PaymentAuditLogMutation) Action() (r string, exists bool) {
+ v := m.action
if v == nil {
return
}
return *v, true
}
-// OldBonusAmount returns the old "bonus_amount" field's value of the PromoCode entity.
-// If the PromoCode object wasn't provided to the builder, the object is fetched from the database.
+// OldAction returns the old "action" field's value of the PaymentAuditLog entity.
+// If the PaymentAuditLog object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
-func (m *PromoCodeMutation) OldBonusAmount(ctx context.Context) (v float64, err error) {
+func (m *PaymentAuditLogMutation) OldAction(ctx context.Context) (v string, err error) {
if !m.op.Is(OpUpdateOne) {
- return v, errors.New("OldBonusAmount is only allowed on UpdateOne operations")
+ return v, errors.New("OldAction is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
- return v, errors.New("OldBonusAmount requires an ID field in the mutation")
+ return v, errors.New("OldAction requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
- return v, fmt.Errorf("querying old value for OldBonusAmount: %w", err)
- }
- return oldValue.BonusAmount, nil
-}
-
-// AddBonusAmount adds f to the "bonus_amount" field.
-func (m *PromoCodeMutation) AddBonusAmount(f float64) {
- if m.addbonus_amount != nil {
- *m.addbonus_amount += f
- } else {
- m.addbonus_amount = &f
- }
-}
-
-// AddedBonusAmount returns the value that was added to the "bonus_amount" field in this mutation.
-func (m *PromoCodeMutation) AddedBonusAmount() (r float64, exists bool) {
- v := m.addbonus_amount
- if v == nil {
- return
+ return v, fmt.Errorf("querying old value for OldAction: %w", err)
}
- return *v, true
+ return oldValue.Action, nil
}
-// ResetBonusAmount resets all changes to the "bonus_amount" field.
-func (m *PromoCodeMutation) ResetBonusAmount() {
- m.bonus_amount = nil
- m.addbonus_amount = nil
+// ResetAction resets all changes to the "action" field.
+func (m *PaymentAuditLogMutation) ResetAction() {
+ m.action = nil
}
-// SetMaxUses sets the "max_uses" field.
-func (m *PromoCodeMutation) SetMaxUses(i int) {
- m.max_uses = &i
- m.addmax_uses = nil
+// SetDetail sets the "detail" field.
+func (m *PaymentAuditLogMutation) SetDetail(s string) {
+ m.detail = &s
}
-// MaxUses returns the value of the "max_uses" field in the mutation.
-func (m *PromoCodeMutation) MaxUses() (r int, exists bool) {
- v := m.max_uses
+// Detail returns the value of the "detail" field in the mutation.
+func (m *PaymentAuditLogMutation) Detail() (r string, exists bool) {
+ v := m.detail
if v == nil {
return
}
return *v, true
}
-// OldMaxUses returns the old "max_uses" field's value of the PromoCode entity.
-// If the PromoCode object wasn't provided to the builder, the object is fetched from the database.
+// OldDetail returns the old "detail" field's value of the PaymentAuditLog entity.
+// If the PaymentAuditLog object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
-func (m *PromoCodeMutation) OldMaxUses(ctx context.Context) (v int, err error) {
+func (m *PaymentAuditLogMutation) OldDetail(ctx context.Context) (v string, err error) {
if !m.op.Is(OpUpdateOne) {
- return v, errors.New("OldMaxUses is only allowed on UpdateOne operations")
+ return v, errors.New("OldDetail is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
- return v, errors.New("OldMaxUses requires an ID field in the mutation")
+ return v, errors.New("OldDetail requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
- return v, fmt.Errorf("querying old value for OldMaxUses: %w", err)
- }
- return oldValue.MaxUses, nil
-}
-
-// AddMaxUses adds i to the "max_uses" field.
-func (m *PromoCodeMutation) AddMaxUses(i int) {
- if m.addmax_uses != nil {
- *m.addmax_uses += i
- } else {
- m.addmax_uses = &i
- }
-}
-
-// AddedMaxUses returns the value that was added to the "max_uses" field in this mutation.
-func (m *PromoCodeMutation) AddedMaxUses() (r int, exists bool) {
- v := m.addmax_uses
- if v == nil {
- return
+ return v, fmt.Errorf("querying old value for OldDetail: %w", err)
}
- return *v, true
+ return oldValue.Detail, nil
}
-// ResetMaxUses resets all changes to the "max_uses" field.
-func (m *PromoCodeMutation) ResetMaxUses() {
- m.max_uses = nil
- m.addmax_uses = nil
+// ResetDetail resets all changes to the "detail" field.
+func (m *PaymentAuditLogMutation) ResetDetail() {
+ m.detail = nil
}
-// SetUsedCount sets the "used_count" field.
-func (m *PromoCodeMutation) SetUsedCount(i int) {
- m.used_count = &i
- m.addused_count = nil
+// SetOperator sets the "operator" field.
+func (m *PaymentAuditLogMutation) SetOperator(s string) {
+ m.operator = &s
}
-// UsedCount returns the value of the "used_count" field in the mutation.
-func (m *PromoCodeMutation) UsedCount() (r int, exists bool) {
- v := m.used_count
+// Operator returns the value of the "operator" field in the mutation.
+func (m *PaymentAuditLogMutation) Operator() (r string, exists bool) {
+ v := m.operator
if v == nil {
return
}
return *v, true
}
-// OldUsedCount returns the old "used_count" field's value of the PromoCode entity.
-// If the PromoCode object wasn't provided to the builder, the object is fetched from the database.
+// OldOperator returns the old "operator" field's value of the PaymentAuditLog entity.
+// If the PaymentAuditLog object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
-func (m *PromoCodeMutation) OldUsedCount(ctx context.Context) (v int, err error) {
+func (m *PaymentAuditLogMutation) OldOperator(ctx context.Context) (v string, err error) {
if !m.op.Is(OpUpdateOne) {
- return v, errors.New("OldUsedCount is only allowed on UpdateOne operations")
+ return v, errors.New("OldOperator is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
- return v, errors.New("OldUsedCount requires an ID field in the mutation")
+ return v, errors.New("OldOperator requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
- return v, fmt.Errorf("querying old value for OldUsedCount: %w", err)
- }
- return oldValue.UsedCount, nil
-}
-
-// AddUsedCount adds i to the "used_count" field.
-func (m *PromoCodeMutation) AddUsedCount(i int) {
- if m.addused_count != nil {
- *m.addused_count += i
- } else {
- m.addused_count = &i
- }
-}
-
-// AddedUsedCount returns the value that was added to the "used_count" field in this mutation.
-func (m *PromoCodeMutation) AddedUsedCount() (r int, exists bool) {
- v := m.addused_count
- if v == nil {
- return
+ return v, fmt.Errorf("querying old value for OldOperator: %w", err)
}
- return *v, true
+ return oldValue.Operator, nil
}
-// ResetUsedCount resets all changes to the "used_count" field.
-func (m *PromoCodeMutation) ResetUsedCount() {
- m.used_count = nil
- m.addused_count = nil
+// ResetOperator resets all changes to the "operator" field.
+func (m *PaymentAuditLogMutation) ResetOperator() {
+ m.operator = nil
}
-// SetStatus sets the "status" field.
-func (m *PromoCodeMutation) SetStatus(s string) {
- m.status = &s
+// SetCreatedAt sets the "created_at" field.
+func (m *PaymentAuditLogMutation) SetCreatedAt(t time.Time) {
+ m.created_at = &t
}
-// Status returns the value of the "status" field in the mutation.
-func (m *PromoCodeMutation) Status() (r string, exists bool) {
- v := m.status
+// CreatedAt returns the value of the "created_at" field in the mutation.
+func (m *PaymentAuditLogMutation) CreatedAt() (r time.Time, exists bool) {
+ v := m.created_at
if v == nil {
return
}
return *v, true
}
-// OldStatus returns the old "status" field's value of the PromoCode entity.
-// If the PromoCode object wasn't provided to the builder, the object is fetched from the database.
+// OldCreatedAt returns the old "created_at" field's value of the PaymentAuditLog entity.
+// If the PaymentAuditLog object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
-func (m *PromoCodeMutation) OldStatus(ctx context.Context) (v string, err error) {
+func (m *PaymentAuditLogMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) {
if !m.op.Is(OpUpdateOne) {
- return v, errors.New("OldStatus is only allowed on UpdateOne operations")
+ return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
- return v, errors.New("OldStatus requires an ID field in the mutation")
+ return v, errors.New("OldCreatedAt requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
- return v, fmt.Errorf("querying old value for OldStatus: %w", err)
+ return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err)
}
- return oldValue.Status, nil
+ return oldValue.CreatedAt, nil
}
-// ResetStatus resets all changes to the "status" field.
-func (m *PromoCodeMutation) ResetStatus() {
- m.status = nil
+// ResetCreatedAt resets all changes to the "created_at" field.
+func (m *PaymentAuditLogMutation) ResetCreatedAt() {
+ m.created_at = nil
}
-// SetExpiresAt sets the "expires_at" field.
-func (m *PromoCodeMutation) SetExpiresAt(t time.Time) {
- m.expires_at = &t
+// Where appends a list predicates to the PaymentAuditLogMutation builder.
+func (m *PaymentAuditLogMutation) Where(ps ...predicate.PaymentAuditLog) {
+ m.predicates = append(m.predicates, ps...)
}
-// ExpiresAt returns the value of the "expires_at" field in the mutation.
-func (m *PromoCodeMutation) ExpiresAt() (r time.Time, exists bool) {
- v := m.expires_at
- if v == nil {
- return
+// WhereP appends storage-level predicates to the PaymentAuditLogMutation builder. Using this method,
+// users can use type-assertion to append predicates that do not depend on any generated package.
+func (m *PaymentAuditLogMutation) WhereP(ps ...func(*sql.Selector)) {
+ p := make([]predicate.PaymentAuditLog, len(ps))
+ for i := range ps {
+ p[i] = ps[i]
}
- return *v, true
+ m.Where(p...)
}
-// OldExpiresAt returns the old "expires_at" field's value of the PromoCode entity.
-// If the PromoCode object wasn't provided to the builder, the object is fetched from the database.
-// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
-func (m *PromoCodeMutation) OldExpiresAt(ctx context.Context) (v *time.Time, err error) {
- if !m.op.Is(OpUpdateOne) {
- return v, errors.New("OldExpiresAt is only allowed on UpdateOne operations")
- }
- if m.id == nil || m.oldValue == nil {
- return v, errors.New("OldExpiresAt requires an ID field in the mutation")
- }
- oldValue, err := m.oldValue(ctx)
- if err != nil {
- return v, fmt.Errorf("querying old value for OldExpiresAt: %w", err)
- }
- return oldValue.ExpiresAt, nil
-}
-
-// ClearExpiresAt clears the value of the "expires_at" field.
-func (m *PromoCodeMutation) ClearExpiresAt() {
- m.expires_at = nil
- m.clearedFields[promocode.FieldExpiresAt] = struct{}{}
-}
-
-// ExpiresAtCleared returns if the "expires_at" field was cleared in this mutation.
-func (m *PromoCodeMutation) ExpiresAtCleared() bool {
- _, ok := m.clearedFields[promocode.FieldExpiresAt]
- return ok
+// Op returns the operation name.
+func (m *PaymentAuditLogMutation) Op() Op {
+ return m.op
}
-// ResetExpiresAt resets all changes to the "expires_at" field.
-func (m *PromoCodeMutation) ResetExpiresAt() {
- m.expires_at = nil
- delete(m.clearedFields, promocode.FieldExpiresAt)
+// SetOp allows setting the mutation operation.
+func (m *PaymentAuditLogMutation) SetOp(op Op) {
+ m.op = op
}
-// SetNotes sets the "notes" field.
-func (m *PromoCodeMutation) SetNotes(s string) {
- m.notes = &s
+// Type returns the node type of this mutation (PaymentAuditLog).
+func (m *PaymentAuditLogMutation) Type() string {
+ return m.typ
}
-// Notes returns the value of the "notes" field in the mutation.
-func (m *PromoCodeMutation) Notes() (r string, exists bool) {
- v := m.notes
- if v == nil {
- return
+// Fields returns all fields that were changed during this mutation. Note that in
+// order to get all numeric fields that were incremented/decremented, call
+// AddedFields().
+func (m *PaymentAuditLogMutation) Fields() []string {
+ fields := make([]string, 0, 5)
+ if m.order_id != nil {
+ fields = append(fields, paymentauditlog.FieldOrderID)
}
- return *v, true
-}
-
-// OldNotes returns the old "notes" field's value of the PromoCode entity.
-// If the PromoCode object wasn't provided to the builder, the object is fetched from the database.
-// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
-func (m *PromoCodeMutation) OldNotes(ctx context.Context) (v *string, err error) {
- if !m.op.Is(OpUpdateOne) {
- return v, errors.New("OldNotes is only allowed on UpdateOne operations")
+ if m.action != nil {
+ fields = append(fields, paymentauditlog.FieldAction)
}
- if m.id == nil || m.oldValue == nil {
- return v, errors.New("OldNotes requires an ID field in the mutation")
+ if m.detail != nil {
+ fields = append(fields, paymentauditlog.FieldDetail)
}
- oldValue, err := m.oldValue(ctx)
- if err != nil {
- return v, fmt.Errorf("querying old value for OldNotes: %w", err)
+ if m.operator != nil {
+ fields = append(fields, paymentauditlog.FieldOperator)
}
- return oldValue.Notes, nil
+ if m.created_at != nil {
+ fields = append(fields, paymentauditlog.FieldCreatedAt)
+ }
+ return fields
}
-// ClearNotes clears the value of the "notes" field.
-func (m *PromoCodeMutation) ClearNotes() {
- m.notes = nil
- m.clearedFields[promocode.FieldNotes] = struct{}{}
+// Field returns the value of a field with the given name. The second boolean
+// return value indicates that this field was not set, or was not defined in the
+// schema.
+func (m *PaymentAuditLogMutation) Field(name string) (ent.Value, bool) {
+ switch name {
+ case paymentauditlog.FieldOrderID:
+ return m.OrderID()
+ case paymentauditlog.FieldAction:
+ return m.Action()
+ case paymentauditlog.FieldDetail:
+ return m.Detail()
+ case paymentauditlog.FieldOperator:
+ return m.Operator()
+ case paymentauditlog.FieldCreatedAt:
+ return m.CreatedAt()
+ }
+ return nil, false
}
-// NotesCleared returns if the "notes" field was cleared in this mutation.
-func (m *PromoCodeMutation) NotesCleared() bool {
- _, ok := m.clearedFields[promocode.FieldNotes]
- return ok
+// OldField returns the old value of the field from the database. An error is
+// returned if the mutation operation is not UpdateOne, or the query to the
+// database failed.
+func (m *PaymentAuditLogMutation) OldField(ctx context.Context, name string) (ent.Value, error) {
+ switch name {
+ case paymentauditlog.FieldOrderID:
+ return m.OldOrderID(ctx)
+ case paymentauditlog.FieldAction:
+ return m.OldAction(ctx)
+ case paymentauditlog.FieldDetail:
+ return m.OldDetail(ctx)
+ case paymentauditlog.FieldOperator:
+ return m.OldOperator(ctx)
+ case paymentauditlog.FieldCreatedAt:
+ return m.OldCreatedAt(ctx)
+ }
+ return nil, fmt.Errorf("unknown PaymentAuditLog field %s", name)
}
-// ResetNotes resets all changes to the "notes" field.
-func (m *PromoCodeMutation) ResetNotes() {
- m.notes = nil
- delete(m.clearedFields, promocode.FieldNotes)
+// SetField sets the value of a field with the given name. It returns an error if
+// the field is not defined in the schema, or if the type mismatched the field
+// type.
+func (m *PaymentAuditLogMutation) SetField(name string, value ent.Value) error {
+ switch name {
+ case paymentauditlog.FieldOrderID:
+ v, ok := value.(string)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetOrderID(v)
+ return nil
+ case paymentauditlog.FieldAction:
+ v, ok := value.(string)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetAction(v)
+ return nil
+ case paymentauditlog.FieldDetail:
+ v, ok := value.(string)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetDetail(v)
+ return nil
+ case paymentauditlog.FieldOperator:
+ v, ok := value.(string)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetOperator(v)
+ return nil
+ case paymentauditlog.FieldCreatedAt:
+ v, ok := value.(time.Time)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetCreatedAt(v)
+ return nil
+ }
+ return fmt.Errorf("unknown PaymentAuditLog field %s", name)
}
-// SetCreatedAt sets the "created_at" field.
-func (m *PromoCodeMutation) SetCreatedAt(t time.Time) {
- m.created_at = &t
+// AddedFields returns all numeric fields that were incremented/decremented during
+// this mutation.
+func (m *PaymentAuditLogMutation) AddedFields() []string {
+ return nil
}
-// CreatedAt returns the value of the "created_at" field in the mutation.
-func (m *PromoCodeMutation) CreatedAt() (r time.Time, exists bool) {
- v := m.created_at
- if v == nil {
- return
- }
- return *v, true
+// AddedField returns the numeric value that was incremented/decremented on a field
+// with the given name. The second boolean return value indicates that this field
+// was not set, or was not defined in the schema.
+func (m *PaymentAuditLogMutation) AddedField(name string) (ent.Value, bool) {
+ return nil, false
}
-// OldCreatedAt returns the old "created_at" field's value of the PromoCode entity.
-// If the PromoCode object wasn't provided to the builder, the object is fetched from the database.
-// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
-func (m *PromoCodeMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) {
- if !m.op.Is(OpUpdateOne) {
- return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations")
- }
- if m.id == nil || m.oldValue == nil {
- return v, errors.New("OldCreatedAt requires an ID field in the mutation")
- }
- oldValue, err := m.oldValue(ctx)
- if err != nil {
- return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err)
+// AddField adds the value to the field with the given name. It returns an error if
+// the field is not defined in the schema, or if the type mismatched the field
+// type.
+func (m *PaymentAuditLogMutation) AddField(name string, value ent.Value) error {
+ switch name {
}
- return oldValue.CreatedAt, nil
+ return fmt.Errorf("unknown PaymentAuditLog numeric field %s", name)
}
-// ResetCreatedAt resets all changes to the "created_at" field.
-func (m *PromoCodeMutation) ResetCreatedAt() {
- m.created_at = nil
+// ClearedFields returns all nullable fields that were cleared during this
+// mutation.
+func (m *PaymentAuditLogMutation) ClearedFields() []string {
+ return nil
}
-// SetUpdatedAt sets the "updated_at" field.
-func (m *PromoCodeMutation) SetUpdatedAt(t time.Time) {
- m.updated_at = &t
+// FieldCleared returns a boolean indicating if a field with the given name was
+// cleared in this mutation.
+func (m *PaymentAuditLogMutation) FieldCleared(name string) bool {
+ _, ok := m.clearedFields[name]
+ return ok
}
-// UpdatedAt returns the value of the "updated_at" field in the mutation.
-func (m *PromoCodeMutation) UpdatedAt() (r time.Time, exists bool) {
- v := m.updated_at
- if v == nil {
- return
- }
- return *v, true
+// ClearField clears the value of the field with the given name. It returns an
+// error if the field is not defined in the schema.
+func (m *PaymentAuditLogMutation) ClearField(name string) error {
+ return fmt.Errorf("unknown PaymentAuditLog nullable field %s", name)
}
-// OldUpdatedAt returns the old "updated_at" field's value of the PromoCode entity.
-// If the PromoCode object wasn't provided to the builder, the object is fetched from the database.
-// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
-func (m *PromoCodeMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) {
- if !m.op.Is(OpUpdateOne) {
- return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations")
- }
- if m.id == nil || m.oldValue == nil {
- return v, errors.New("OldUpdatedAt requires an ID field in the mutation")
- }
- oldValue, err := m.oldValue(ctx)
- if err != nil {
- return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err)
+// ResetField resets all changes in the mutation for the field with the given name.
+// It returns an error if the field is not defined in the schema.
+func (m *PaymentAuditLogMutation) ResetField(name string) error {
+ switch name {
+ case paymentauditlog.FieldOrderID:
+ m.ResetOrderID()
+ return nil
+ case paymentauditlog.FieldAction:
+ m.ResetAction()
+ return nil
+ case paymentauditlog.FieldDetail:
+ m.ResetDetail()
+ return nil
+ case paymentauditlog.FieldOperator:
+ m.ResetOperator()
+ return nil
+ case paymentauditlog.FieldCreatedAt:
+ m.ResetCreatedAt()
+ return nil
}
- return oldValue.UpdatedAt, nil
+ return fmt.Errorf("unknown PaymentAuditLog field %s", name)
}
-// ResetUpdatedAt resets all changes to the "updated_at" field.
-func (m *PromoCodeMutation) ResetUpdatedAt() {
- m.updated_at = nil
+// AddedEdges returns all edge names that were set/added in this mutation.
+func (m *PaymentAuditLogMutation) AddedEdges() []string {
+ edges := make([]string, 0, 0)
+ return edges
}
-// AddUsageRecordIDs adds the "usage_records" edge to the PromoCodeUsage entity by ids.
-func (m *PromoCodeMutation) AddUsageRecordIDs(ids ...int64) {
- if m.usage_records == nil {
- m.usage_records = make(map[int64]struct{})
- }
- for i := range ids {
- m.usage_records[ids[i]] = struct{}{}
- }
+// AddedIDs returns all IDs (to other nodes) that were added for the given edge
+// name in this mutation.
+func (m *PaymentAuditLogMutation) AddedIDs(name string) []ent.Value {
+ return nil
}
-// ClearUsageRecords clears the "usage_records" edge to the PromoCodeUsage entity.
-func (m *PromoCodeMutation) ClearUsageRecords() {
- m.clearedusage_records = true
+// RemovedEdges returns all edge names that were removed in this mutation.
+func (m *PaymentAuditLogMutation) RemovedEdges() []string {
+ edges := make([]string, 0, 0)
+ return edges
}
-// UsageRecordsCleared reports if the "usage_records" edge to the PromoCodeUsage entity was cleared.
-func (m *PromoCodeMutation) UsageRecordsCleared() bool {
- return m.clearedusage_records
+// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with
+// the given name in this mutation.
+func (m *PaymentAuditLogMutation) RemovedIDs(name string) []ent.Value {
+ return nil
}
-// RemoveUsageRecordIDs removes the "usage_records" edge to the PromoCodeUsage entity by IDs.
-func (m *PromoCodeMutation) RemoveUsageRecordIDs(ids ...int64) {
- if m.removedusage_records == nil {
- m.removedusage_records = make(map[int64]struct{})
- }
- for i := range ids {
- delete(m.usage_records, ids[i])
- m.removedusage_records[ids[i]] = struct{}{}
- }
+// ClearedEdges returns all edge names that were cleared in this mutation.
+func (m *PaymentAuditLogMutation) ClearedEdges() []string {
+ edges := make([]string, 0, 0)
+ return edges
}
-// RemovedUsageRecords returns the removed IDs of the "usage_records" edge to the PromoCodeUsage entity.
-func (m *PromoCodeMutation) RemovedUsageRecordsIDs() (ids []int64) {
- for id := range m.removedusage_records {
- ids = append(ids, id)
- }
- return
+// EdgeCleared returns a boolean which indicates if the edge with the given name
+// was cleared in this mutation.
+func (m *PaymentAuditLogMutation) EdgeCleared(name string) bool {
+ return false
}
-// UsageRecordsIDs returns the "usage_records" edge IDs in the mutation.
-func (m *PromoCodeMutation) UsageRecordsIDs() (ids []int64) {
- for id := range m.usage_records {
- ids = append(ids, id)
- }
- return
+// ClearEdge clears the value of the edge with the given name. It returns an error
+// if that edge is not defined in the schema.
+func (m *PaymentAuditLogMutation) ClearEdge(name string) error {
+ return fmt.Errorf("unknown PaymentAuditLog unique edge %s", name)
}
-// ResetUsageRecords resets all changes to the "usage_records" edge.
-func (m *PromoCodeMutation) ResetUsageRecords() {
- m.usage_records = nil
- m.clearedusage_records = false
- m.removedusage_records = nil
+// ResetEdge resets all changes to the edge with the given name in this mutation.
+// It returns an error if the edge is not defined in the schema.
+func (m *PaymentAuditLogMutation) ResetEdge(name string) error {
+ return fmt.Errorf("unknown PaymentAuditLog edge %s", name)
}
-// Where appends a list predicates to the PromoCodeMutation builder.
-func (m *PromoCodeMutation) Where(ps ...predicate.PromoCode) {
- m.predicates = append(m.predicates, ps...)
+// PaymentOrderMutation represents an operation that mutates the PaymentOrder nodes in the graph.
+type PaymentOrderMutation struct {
+ config
+ op Op
+ typ string
+ id *int64
+ user_email *string
+ user_name *string
+ user_notes *string
+ amount *float64
+ addamount *float64
+ pay_amount *float64
+ addpay_amount *float64
+ fee_rate *float64
+ addfee_rate *float64
+ recharge_code *string
+ out_trade_no *string
+ payment_type *string
+ payment_trade_no *string
+ pay_url *string
+ qr_code *string
+ qr_code_img *string
+ order_type *string
+ plan_id *int64
+ addplan_id *int64
+ subscription_group_id *int64
+ addsubscription_group_id *int64
+ subscription_days *int
+ addsubscription_days *int
+ provider_instance_id *string
+ status *string
+ refund_amount *float64
+ addrefund_amount *float64
+ refund_reason *string
+ refund_at *time.Time
+ force_refund *bool
+ refund_requested_at *time.Time
+ refund_request_reason *string
+ refund_requested_by *string
+ expires_at *time.Time
+ paid_at *time.Time
+ completed_at *time.Time
+ failed_at *time.Time
+ failed_reason *string
+ client_ip *string
+ src_host *string
+ src_url *string
+ created_at *time.Time
+ updated_at *time.Time
+ clearedFields map[string]struct{}
+ user *int64
+ cleareduser bool
+ done bool
+ oldValue func(context.Context) (*PaymentOrder, error)
+ predicates []predicate.PaymentOrder
+}
+
+var _ ent.Mutation = (*PaymentOrderMutation)(nil)
+
+// paymentorderOption allows management of the mutation configuration using functional options.
+type paymentorderOption func(*PaymentOrderMutation)
+
+// newPaymentOrderMutation creates new mutation for the PaymentOrder entity.
+func newPaymentOrderMutation(c config, op Op, opts ...paymentorderOption) *PaymentOrderMutation {
+ m := &PaymentOrderMutation{
+ config: c,
+ op: op,
+ typ: TypePaymentOrder,
+ clearedFields: make(map[string]struct{}),
+ }
+ for _, opt := range opts {
+ opt(m)
+ }
+ return m
}
-// WhereP appends storage-level predicates to the PromoCodeMutation builder. Using this method,
-// users can use type-assertion to append predicates that do not depend on any generated package.
-func (m *PromoCodeMutation) WhereP(ps ...func(*sql.Selector)) {
- p := make([]predicate.PromoCode, len(ps))
- for i := range ps {
- p[i] = ps[i]
+// withPaymentOrderID sets the ID field of the mutation.
+func withPaymentOrderID(id int64) paymentorderOption {
+ return func(m *PaymentOrderMutation) {
+ var (
+ err error
+ once sync.Once
+ value *PaymentOrder
+ )
+ m.oldValue = func(ctx context.Context) (*PaymentOrder, error) {
+ once.Do(func() {
+ if m.done {
+ err = errors.New("querying old values post mutation is not allowed")
+ } else {
+ value, err = m.Client().PaymentOrder.Get(ctx, id)
+ }
+ })
+ return value, err
+ }
+ m.id = &id
}
- m.Where(p...)
}
-// Op returns the operation name.
-func (m *PromoCodeMutation) Op() Op {
- return m.op
+// withPaymentOrder sets the old PaymentOrder of the mutation.
+func withPaymentOrder(node *PaymentOrder) paymentorderOption {
+ return func(m *PaymentOrderMutation) {
+ m.oldValue = func(context.Context) (*PaymentOrder, error) {
+ return node, nil
+ }
+ m.id = &node.ID
+ }
}
-// SetOp allows setting the mutation operation.
-func (m *PromoCodeMutation) SetOp(op Op) {
- m.op = op
+// Client returns a new `ent.Client` from the mutation. If the mutation was
+// executed in a transaction (ent.Tx), a transactional client is returned.
+func (m PaymentOrderMutation) Client() *Client {
+ client := &Client{config: m.config}
+ client.init()
+ return client
}
-// Type returns the node type of this mutation (PromoCode).
-func (m *PromoCodeMutation) Type() string {
- return m.typ
+// Tx returns an `ent.Tx` for mutations that were executed in transactions;
+// it returns an error otherwise.
+func (m PaymentOrderMutation) Tx() (*Tx, error) {
+ if _, ok := m.driver.(*txDriver); !ok {
+ return nil, errors.New("ent: mutation is not running in a transaction")
+ }
+ tx := &Tx{config: m.config}
+ tx.init()
+ return tx, nil
}
-// Fields returns all fields that were changed during this mutation. Note that in
-// order to get all numeric fields that were incremented/decremented, call
-// AddedFields().
-func (m *PromoCodeMutation) Fields() []string {
- fields := make([]string, 0, 9)
- if m.code != nil {
- fields = append(fields, promocode.FieldCode)
+// ID returns the ID value in the mutation. Note that the ID is only available
+// if it was provided to the builder or after it was returned from the database.
+func (m *PaymentOrderMutation) ID() (id int64, exists bool) {
+ if m.id == nil {
+ return
}
- if m.bonus_amount != nil {
- fields = append(fields, promocode.FieldBonusAmount)
+ return *m.id, true
+}
+
+// IDs queries the database and returns the entity ids that match the mutation's predicate.
+// That means, if the mutation is applied within a transaction with an isolation level such
+// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated
+// or updated by the mutation.
+func (m *PaymentOrderMutation) IDs(ctx context.Context) ([]int64, error) {
+ switch {
+ case m.op.Is(OpUpdateOne | OpDeleteOne):
+ id, exists := m.ID()
+ if exists {
+ return []int64{id}, nil
+ }
+ fallthrough
+ case m.op.Is(OpUpdate | OpDelete):
+ return m.Client().PaymentOrder.Query().Where(m.predicates...).IDs(ctx)
+ default:
+ return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op)
}
- if m.max_uses != nil {
- fields = append(fields, promocode.FieldMaxUses)
+}
+
+// SetUserID sets the "user_id" field.
+func (m *PaymentOrderMutation) SetUserID(i int64) {
+ m.user = &i
+}
+
+// UserID returns the value of the "user_id" field in the mutation.
+func (m *PaymentOrderMutation) UserID() (r int64, exists bool) {
+ v := m.user
+ if v == nil {
+ return
}
- if m.used_count != nil {
- fields = append(fields, promocode.FieldUsedCount)
+ return *v, true
+}
+
+// OldUserID returns the old "user_id" field's value of the PaymentOrder entity.
+// If the PaymentOrder object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PaymentOrderMutation) OldUserID(ctx context.Context) (v int64, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldUserID is only allowed on UpdateOne operations")
}
- if m.status != nil {
- fields = append(fields, promocode.FieldStatus)
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldUserID requires an ID field in the mutation")
}
- if m.expires_at != nil {
- fields = append(fields, promocode.FieldExpiresAt)
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldUserID: %w", err)
}
- if m.notes != nil {
- fields = append(fields, promocode.FieldNotes)
+ return oldValue.UserID, nil
+}
+
+// ResetUserID resets all changes to the "user_id" field.
+func (m *PaymentOrderMutation) ResetUserID() {
+ m.user = nil
+}
+
+// SetUserEmail sets the "user_email" field.
+func (m *PaymentOrderMutation) SetUserEmail(s string) {
+ m.user_email = &s
+}
+
+// UserEmail returns the value of the "user_email" field in the mutation.
+func (m *PaymentOrderMutation) UserEmail() (r string, exists bool) {
+ v := m.user_email
+ if v == nil {
+ return
}
- if m.created_at != nil {
- fields = append(fields, promocode.FieldCreatedAt)
+ return *v, true
+}
+
+// OldUserEmail returns the old "user_email" field's value of the PaymentOrder entity.
+// If the PaymentOrder object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PaymentOrderMutation) OldUserEmail(ctx context.Context) (v string, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldUserEmail is only allowed on UpdateOne operations")
}
- if m.updated_at != nil {
- fields = append(fields, promocode.FieldUpdatedAt)
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldUserEmail requires an ID field in the mutation")
}
- return fields
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldUserEmail: %w", err)
+ }
+ return oldValue.UserEmail, nil
}
-// Field returns the value of a field with the given name. The second boolean
-// return value indicates that this field was not set, or was not defined in the
-// schema.
-func (m *PromoCodeMutation) Field(name string) (ent.Value, bool) {
- switch name {
- case promocode.FieldCode:
- return m.Code()
- case promocode.FieldBonusAmount:
- return m.BonusAmount()
- case promocode.FieldMaxUses:
- return m.MaxUses()
- case promocode.FieldUsedCount:
- return m.UsedCount()
- case promocode.FieldStatus:
- return m.Status()
- case promocode.FieldExpiresAt:
- return m.ExpiresAt()
- case promocode.FieldNotes:
- return m.Notes()
- case promocode.FieldCreatedAt:
- return m.CreatedAt()
- case promocode.FieldUpdatedAt:
- return m.UpdatedAt()
+// ResetUserEmail resets all changes to the "user_email" field.
+func (m *PaymentOrderMutation) ResetUserEmail() {
+ m.user_email = nil
+}
+
+// SetUserName sets the "user_name" field.
+func (m *PaymentOrderMutation) SetUserName(s string) {
+ m.user_name = &s
+}
+
+// UserName returns the value of the "user_name" field in the mutation.
+func (m *PaymentOrderMutation) UserName() (r string, exists bool) {
+ v := m.user_name
+ if v == nil {
+ return
}
- return nil, false
+ return *v, true
}
-// OldField returns the old value of the field from the database. An error is
-// returned if the mutation operation is not UpdateOne, or the query to the
-// database failed.
-func (m *PromoCodeMutation) OldField(ctx context.Context, name string) (ent.Value, error) {
- switch name {
- case promocode.FieldCode:
- return m.OldCode(ctx)
- case promocode.FieldBonusAmount:
- return m.OldBonusAmount(ctx)
- case promocode.FieldMaxUses:
- return m.OldMaxUses(ctx)
- case promocode.FieldUsedCount:
- return m.OldUsedCount(ctx)
- case promocode.FieldStatus:
- return m.OldStatus(ctx)
- case promocode.FieldExpiresAt:
- return m.OldExpiresAt(ctx)
- case promocode.FieldNotes:
- return m.OldNotes(ctx)
- case promocode.FieldCreatedAt:
- return m.OldCreatedAt(ctx)
- case promocode.FieldUpdatedAt:
- return m.OldUpdatedAt(ctx)
+// OldUserName returns the old "user_name" field's value of the PaymentOrder entity.
+// If the PaymentOrder object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PaymentOrderMutation) OldUserName(ctx context.Context) (v string, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldUserName is only allowed on UpdateOne operations")
}
- return nil, fmt.Errorf("unknown PromoCode field %s", name)
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldUserName requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldUserName: %w", err)
+ }
+ return oldValue.UserName, nil
}
-// SetField sets the value of a field with the given name. It returns an error if
-// the field is not defined in the schema, or if the type mismatched the field
-// type.
-func (m *PromoCodeMutation) SetField(name string, value ent.Value) error {
- switch name {
- case promocode.FieldCode:
- v, ok := value.(string)
- if !ok {
- return fmt.Errorf("unexpected type %T for field %s", value, name)
- }
- m.SetCode(v)
- return nil
- case promocode.FieldBonusAmount:
- v, ok := value.(float64)
- if !ok {
- return fmt.Errorf("unexpected type %T for field %s", value, name)
- }
- m.SetBonusAmount(v)
- return nil
- case promocode.FieldMaxUses:
- v, ok := value.(int)
- if !ok {
- return fmt.Errorf("unexpected type %T for field %s", value, name)
- }
- m.SetMaxUses(v)
- return nil
- case promocode.FieldUsedCount:
- v, ok := value.(int)
- if !ok {
- return fmt.Errorf("unexpected type %T for field %s", value, name)
- }
- m.SetUsedCount(v)
- return nil
- case promocode.FieldStatus:
- v, ok := value.(string)
- if !ok {
- return fmt.Errorf("unexpected type %T for field %s", value, name)
- }
- m.SetStatus(v)
- return nil
- case promocode.FieldExpiresAt:
- v, ok := value.(time.Time)
- if !ok {
- return fmt.Errorf("unexpected type %T for field %s", value, name)
- }
- m.SetExpiresAt(v)
- return nil
- case promocode.FieldNotes:
- v, ok := value.(string)
- if !ok {
- return fmt.Errorf("unexpected type %T for field %s", value, name)
- }
+// ResetUserName resets all changes to the "user_name" field.
+func (m *PaymentOrderMutation) ResetUserName() {
+ m.user_name = nil
+}
+
+// SetUserNotes sets the "user_notes" field.
+func (m *PaymentOrderMutation) SetUserNotes(s string) {
+ m.user_notes = &s
+}
+
+// UserNotes returns the value of the "user_notes" field in the mutation.
+func (m *PaymentOrderMutation) UserNotes() (r string, exists bool) {
+ v := m.user_notes
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldUserNotes returns the old "user_notes" field's value of the PaymentOrder entity.
+// If the PaymentOrder object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PaymentOrderMutation) OldUserNotes(ctx context.Context) (v *string, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldUserNotes is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldUserNotes requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldUserNotes: %w", err)
+ }
+ return oldValue.UserNotes, nil
+}
+
+// ClearUserNotes clears the value of the "user_notes" field.
+func (m *PaymentOrderMutation) ClearUserNotes() {
+ m.user_notes = nil
+ m.clearedFields[paymentorder.FieldUserNotes] = struct{}{}
+}
+
+// UserNotesCleared returns if the "user_notes" field was cleared in this mutation.
+func (m *PaymentOrderMutation) UserNotesCleared() bool {
+ _, ok := m.clearedFields[paymentorder.FieldUserNotes]
+ return ok
+}
+
+// ResetUserNotes resets all changes to the "user_notes" field.
+func (m *PaymentOrderMutation) ResetUserNotes() {
+ m.user_notes = nil
+ delete(m.clearedFields, paymentorder.FieldUserNotes)
+}
+
+// SetAmount sets the "amount" field.
+func (m *PaymentOrderMutation) SetAmount(f float64) {
+ m.amount = &f
+ m.addamount = nil
+}
+
+// Amount returns the value of the "amount" field in the mutation.
+func (m *PaymentOrderMutation) Amount() (r float64, exists bool) {
+ v := m.amount
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldAmount returns the old "amount" field's value of the PaymentOrder entity.
+// If the PaymentOrder object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PaymentOrderMutation) OldAmount(ctx context.Context) (v float64, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldAmount is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldAmount requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldAmount: %w", err)
+ }
+ return oldValue.Amount, nil
+}
+
+// AddAmount adds f to the "amount" field.
+func (m *PaymentOrderMutation) AddAmount(f float64) {
+ if m.addamount != nil {
+ *m.addamount += f
+ } else {
+ m.addamount = &f
+ }
+}
+
+// AddedAmount returns the value that was added to the "amount" field in this mutation.
+func (m *PaymentOrderMutation) AddedAmount() (r float64, exists bool) {
+ v := m.addamount
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// ResetAmount resets all changes to the "amount" field.
+func (m *PaymentOrderMutation) ResetAmount() {
+ m.amount = nil
+ m.addamount = nil
+}
+
+// SetPayAmount sets the "pay_amount" field.
+func (m *PaymentOrderMutation) SetPayAmount(f float64) {
+ m.pay_amount = &f
+ m.addpay_amount = nil
+}
+
+// PayAmount returns the value of the "pay_amount" field in the mutation.
+func (m *PaymentOrderMutation) PayAmount() (r float64, exists bool) {
+ v := m.pay_amount
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldPayAmount returns the old "pay_amount" field's value of the PaymentOrder entity.
+// If the PaymentOrder object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PaymentOrderMutation) OldPayAmount(ctx context.Context) (v float64, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldPayAmount is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldPayAmount requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldPayAmount: %w", err)
+ }
+ return oldValue.PayAmount, nil
+}
+
+// AddPayAmount adds f to the "pay_amount" field.
+func (m *PaymentOrderMutation) AddPayAmount(f float64) {
+ if m.addpay_amount != nil {
+ *m.addpay_amount += f
+ } else {
+ m.addpay_amount = &f
+ }
+}
+
+// AddedPayAmount returns the value that was added to the "pay_amount" field in this mutation.
+func (m *PaymentOrderMutation) AddedPayAmount() (r float64, exists bool) {
+ v := m.addpay_amount
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// ResetPayAmount resets all changes to the "pay_amount" field.
+func (m *PaymentOrderMutation) ResetPayAmount() {
+ m.pay_amount = nil
+ m.addpay_amount = nil
+}
+
+// SetFeeRate sets the "fee_rate" field.
+func (m *PaymentOrderMutation) SetFeeRate(f float64) {
+ m.fee_rate = &f
+ m.addfee_rate = nil
+}
+
+// FeeRate returns the value of the "fee_rate" field in the mutation.
+func (m *PaymentOrderMutation) FeeRate() (r float64, exists bool) {
+ v := m.fee_rate
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldFeeRate returns the old "fee_rate" field's value of the PaymentOrder entity.
+// If the PaymentOrder object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PaymentOrderMutation) OldFeeRate(ctx context.Context) (v float64, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldFeeRate is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldFeeRate requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldFeeRate: %w", err)
+ }
+ return oldValue.FeeRate, nil
+}
+
+// AddFeeRate adds f to the "fee_rate" field.
+func (m *PaymentOrderMutation) AddFeeRate(f float64) {
+ if m.addfee_rate != nil {
+ *m.addfee_rate += f
+ } else {
+ m.addfee_rate = &f
+ }
+}
+
+// AddedFeeRate returns the value that was added to the "fee_rate" field in this mutation.
+func (m *PaymentOrderMutation) AddedFeeRate() (r float64, exists bool) {
+ v := m.addfee_rate
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// ResetFeeRate resets all changes to the "fee_rate" field.
+func (m *PaymentOrderMutation) ResetFeeRate() {
+ m.fee_rate = nil
+ m.addfee_rate = nil
+}
+
+// SetRechargeCode sets the "recharge_code" field.
+func (m *PaymentOrderMutation) SetRechargeCode(s string) {
+ m.recharge_code = &s
+}
+
+// RechargeCode returns the value of the "recharge_code" field in the mutation.
+func (m *PaymentOrderMutation) RechargeCode() (r string, exists bool) {
+ v := m.recharge_code
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldRechargeCode returns the old "recharge_code" field's value of the PaymentOrder entity.
+// If the PaymentOrder object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PaymentOrderMutation) OldRechargeCode(ctx context.Context) (v string, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldRechargeCode is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldRechargeCode requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldRechargeCode: %w", err)
+ }
+ return oldValue.RechargeCode, nil
+}
+
+// ResetRechargeCode resets all changes to the "recharge_code" field.
+func (m *PaymentOrderMutation) ResetRechargeCode() {
+ m.recharge_code = nil
+}
+
+// SetOutTradeNo sets the "out_trade_no" field.
+func (m *PaymentOrderMutation) SetOutTradeNo(s string) {
+ m.out_trade_no = &s
+}
+
+// OutTradeNo returns the value of the "out_trade_no" field in the mutation.
+func (m *PaymentOrderMutation) OutTradeNo() (r string, exists bool) {
+ v := m.out_trade_no
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldOutTradeNo returns the old "out_trade_no" field's value of the PaymentOrder entity.
+// If the PaymentOrder object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PaymentOrderMutation) OldOutTradeNo(ctx context.Context) (v string, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldOutTradeNo is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldOutTradeNo requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldOutTradeNo: %w", err)
+ }
+ return oldValue.OutTradeNo, nil
+}
+
+// ResetOutTradeNo resets all changes to the "out_trade_no" field.
+func (m *PaymentOrderMutation) ResetOutTradeNo() {
+ m.out_trade_no = nil
+}
+
+// SetPaymentType sets the "payment_type" field.
+func (m *PaymentOrderMutation) SetPaymentType(s string) {
+ m.payment_type = &s
+}
+
+// PaymentType returns the value of the "payment_type" field in the mutation.
+func (m *PaymentOrderMutation) PaymentType() (r string, exists bool) {
+ v := m.payment_type
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldPaymentType returns the old "payment_type" field's value of the PaymentOrder entity.
+// If the PaymentOrder object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PaymentOrderMutation) OldPaymentType(ctx context.Context) (v string, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldPaymentType is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldPaymentType requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldPaymentType: %w", err)
+ }
+ return oldValue.PaymentType, nil
+}
+
+// ResetPaymentType resets all changes to the "payment_type" field.
+func (m *PaymentOrderMutation) ResetPaymentType() {
+ m.payment_type = nil
+}
+
+// SetPaymentTradeNo sets the "payment_trade_no" field.
+func (m *PaymentOrderMutation) SetPaymentTradeNo(s string) {
+ m.payment_trade_no = &s
+}
+
+// PaymentTradeNo returns the value of the "payment_trade_no" field in the mutation.
+func (m *PaymentOrderMutation) PaymentTradeNo() (r string, exists bool) {
+ v := m.payment_trade_no
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldPaymentTradeNo returns the old "payment_trade_no" field's value of the PaymentOrder entity.
+// If the PaymentOrder object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PaymentOrderMutation) OldPaymentTradeNo(ctx context.Context) (v string, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldPaymentTradeNo is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldPaymentTradeNo requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldPaymentTradeNo: %w", err)
+ }
+ return oldValue.PaymentTradeNo, nil
+}
+
+// ResetPaymentTradeNo resets all changes to the "payment_trade_no" field.
+func (m *PaymentOrderMutation) ResetPaymentTradeNo() {
+ m.payment_trade_no = nil
+}
+
+// SetPayURL sets the "pay_url" field.
+func (m *PaymentOrderMutation) SetPayURL(s string) {
+ m.pay_url = &s
+}
+
+// PayURL returns the value of the "pay_url" field in the mutation.
+func (m *PaymentOrderMutation) PayURL() (r string, exists bool) {
+ v := m.pay_url
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldPayURL returns the old "pay_url" field's value of the PaymentOrder entity.
+// If the PaymentOrder object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PaymentOrderMutation) OldPayURL(ctx context.Context) (v *string, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldPayURL is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldPayURL requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldPayURL: %w", err)
+ }
+ return oldValue.PayURL, nil
+}
+
+// ClearPayURL clears the value of the "pay_url" field.
+func (m *PaymentOrderMutation) ClearPayURL() {
+ m.pay_url = nil
+ m.clearedFields[paymentorder.FieldPayURL] = struct{}{}
+}
+
+// PayURLCleared returns if the "pay_url" field was cleared in this mutation.
+func (m *PaymentOrderMutation) PayURLCleared() bool {
+ _, ok := m.clearedFields[paymentorder.FieldPayURL]
+ return ok
+}
+
+// ResetPayURL resets all changes to the "pay_url" field.
+func (m *PaymentOrderMutation) ResetPayURL() {
+ m.pay_url = nil
+ delete(m.clearedFields, paymentorder.FieldPayURL)
+}
+
+// SetQrCode sets the "qr_code" field.
+func (m *PaymentOrderMutation) SetQrCode(s string) {
+ m.qr_code = &s
+}
+
+// QrCode returns the value of the "qr_code" field in the mutation.
+func (m *PaymentOrderMutation) QrCode() (r string, exists bool) {
+ v := m.qr_code
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldQrCode returns the old "qr_code" field's value of the PaymentOrder entity.
+// If the PaymentOrder object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PaymentOrderMutation) OldQrCode(ctx context.Context) (v *string, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldQrCode is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldQrCode requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldQrCode: %w", err)
+ }
+ return oldValue.QrCode, nil
+}
+
+// ClearQrCode clears the value of the "qr_code" field.
+func (m *PaymentOrderMutation) ClearQrCode() {
+ m.qr_code = nil
+ m.clearedFields[paymentorder.FieldQrCode] = struct{}{}
+}
+
+// QrCodeCleared returns if the "qr_code" field was cleared in this mutation.
+func (m *PaymentOrderMutation) QrCodeCleared() bool {
+ _, ok := m.clearedFields[paymentorder.FieldQrCode]
+ return ok
+}
+
+// ResetQrCode resets all changes to the "qr_code" field.
+func (m *PaymentOrderMutation) ResetQrCode() {
+ m.qr_code = nil
+ delete(m.clearedFields, paymentorder.FieldQrCode)
+}
+
+// SetQrCodeImg sets the "qr_code_img" field.
+func (m *PaymentOrderMutation) SetQrCodeImg(s string) {
+ m.qr_code_img = &s
+}
+
+// QrCodeImg returns the value of the "qr_code_img" field in the mutation.
+func (m *PaymentOrderMutation) QrCodeImg() (r string, exists bool) {
+ v := m.qr_code_img
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldQrCodeImg returns the old "qr_code_img" field's value of the PaymentOrder entity.
+// If the PaymentOrder object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PaymentOrderMutation) OldQrCodeImg(ctx context.Context) (v *string, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldQrCodeImg is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldQrCodeImg requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldQrCodeImg: %w", err)
+ }
+ return oldValue.QrCodeImg, nil
+}
+
+// ClearQrCodeImg clears the value of the "qr_code_img" field.
+func (m *PaymentOrderMutation) ClearQrCodeImg() {
+ m.qr_code_img = nil
+ m.clearedFields[paymentorder.FieldQrCodeImg] = struct{}{}
+}
+
+// QrCodeImgCleared returns if the "qr_code_img" field was cleared in this mutation.
+func (m *PaymentOrderMutation) QrCodeImgCleared() bool {
+ _, ok := m.clearedFields[paymentorder.FieldQrCodeImg]
+ return ok
+}
+
+// ResetQrCodeImg resets all changes to the "qr_code_img" field.
+func (m *PaymentOrderMutation) ResetQrCodeImg() {
+ m.qr_code_img = nil
+ delete(m.clearedFields, paymentorder.FieldQrCodeImg)
+}
+
+// SetOrderType sets the "order_type" field.
+func (m *PaymentOrderMutation) SetOrderType(s string) {
+ m.order_type = &s
+}
+
+// OrderType returns the value of the "order_type" field in the mutation.
+func (m *PaymentOrderMutation) OrderType() (r string, exists bool) {
+ v := m.order_type
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldOrderType returns the old "order_type" field's value of the PaymentOrder entity.
+// If the PaymentOrder object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PaymentOrderMutation) OldOrderType(ctx context.Context) (v string, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldOrderType is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldOrderType requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldOrderType: %w", err)
+ }
+ return oldValue.OrderType, nil
+}
+
+// ResetOrderType resets all changes to the "order_type" field.
+func (m *PaymentOrderMutation) ResetOrderType() {
+ m.order_type = nil
+}
+
+// SetPlanID sets the "plan_id" field.
+func (m *PaymentOrderMutation) SetPlanID(i int64) {
+ m.plan_id = &i
+ m.addplan_id = nil
+}
+
+// PlanID returns the value of the "plan_id" field in the mutation.
+func (m *PaymentOrderMutation) PlanID() (r int64, exists bool) {
+ v := m.plan_id
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldPlanID returns the old "plan_id" field's value of the PaymentOrder entity.
+// If the PaymentOrder object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PaymentOrderMutation) OldPlanID(ctx context.Context) (v *int64, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldPlanID is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldPlanID requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldPlanID: %w", err)
+ }
+ return oldValue.PlanID, nil
+}
+
+// AddPlanID adds i to the "plan_id" field.
+func (m *PaymentOrderMutation) AddPlanID(i int64) {
+ if m.addplan_id != nil {
+ *m.addplan_id += i
+ } else {
+ m.addplan_id = &i
+ }
+}
+
+// AddedPlanID returns the value that was added to the "plan_id" field in this mutation.
+func (m *PaymentOrderMutation) AddedPlanID() (r int64, exists bool) {
+ v := m.addplan_id
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// ClearPlanID clears the value of the "plan_id" field.
+func (m *PaymentOrderMutation) ClearPlanID() {
+ m.plan_id = nil
+ m.addplan_id = nil
+ m.clearedFields[paymentorder.FieldPlanID] = struct{}{}
+}
+
+// PlanIDCleared returns if the "plan_id" field was cleared in this mutation.
+func (m *PaymentOrderMutation) PlanIDCleared() bool {
+ _, ok := m.clearedFields[paymentorder.FieldPlanID]
+ return ok
+}
+
+// ResetPlanID resets all changes to the "plan_id" field.
+func (m *PaymentOrderMutation) ResetPlanID() {
+ m.plan_id = nil
+ m.addplan_id = nil
+ delete(m.clearedFields, paymentorder.FieldPlanID)
+}
+
+// SetSubscriptionGroupID sets the "subscription_group_id" field.
+func (m *PaymentOrderMutation) SetSubscriptionGroupID(i int64) {
+ m.subscription_group_id = &i
+ m.addsubscription_group_id = nil
+}
+
+// SubscriptionGroupID returns the value of the "subscription_group_id" field in the mutation.
+func (m *PaymentOrderMutation) SubscriptionGroupID() (r int64, exists bool) {
+ v := m.subscription_group_id
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldSubscriptionGroupID returns the old "subscription_group_id" field's value of the PaymentOrder entity.
+// If the PaymentOrder object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PaymentOrderMutation) OldSubscriptionGroupID(ctx context.Context) (v *int64, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldSubscriptionGroupID is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldSubscriptionGroupID requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldSubscriptionGroupID: %w", err)
+ }
+ return oldValue.SubscriptionGroupID, nil
+}
+
+// AddSubscriptionGroupID adds i to the "subscription_group_id" field.
+func (m *PaymentOrderMutation) AddSubscriptionGroupID(i int64) {
+ if m.addsubscription_group_id != nil {
+ *m.addsubscription_group_id += i
+ } else {
+ m.addsubscription_group_id = &i
+ }
+}
+
+// AddedSubscriptionGroupID returns the value that was added to the "subscription_group_id" field in this mutation.
+func (m *PaymentOrderMutation) AddedSubscriptionGroupID() (r int64, exists bool) {
+ v := m.addsubscription_group_id
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// ClearSubscriptionGroupID clears the value of the "subscription_group_id" field.
+func (m *PaymentOrderMutation) ClearSubscriptionGroupID() {
+ m.subscription_group_id = nil
+ m.addsubscription_group_id = nil
+ m.clearedFields[paymentorder.FieldSubscriptionGroupID] = struct{}{}
+}
+
+// SubscriptionGroupIDCleared returns if the "subscription_group_id" field was cleared in this mutation.
+func (m *PaymentOrderMutation) SubscriptionGroupIDCleared() bool {
+ _, ok := m.clearedFields[paymentorder.FieldSubscriptionGroupID]
+ return ok
+}
+
+// ResetSubscriptionGroupID resets all changes to the "subscription_group_id" field.
+func (m *PaymentOrderMutation) ResetSubscriptionGroupID() {
+ m.subscription_group_id = nil
+ m.addsubscription_group_id = nil
+ delete(m.clearedFields, paymentorder.FieldSubscriptionGroupID)
+}
+
+// SetSubscriptionDays sets the "subscription_days" field.
+func (m *PaymentOrderMutation) SetSubscriptionDays(i int) {
+ m.subscription_days = &i
+ m.addsubscription_days = nil
+}
+
+// SubscriptionDays returns the value of the "subscription_days" field in the mutation.
+func (m *PaymentOrderMutation) SubscriptionDays() (r int, exists bool) {
+ v := m.subscription_days
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldSubscriptionDays returns the old "subscription_days" field's value of the PaymentOrder entity.
+// If the PaymentOrder object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PaymentOrderMutation) OldSubscriptionDays(ctx context.Context) (v *int, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldSubscriptionDays is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldSubscriptionDays requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldSubscriptionDays: %w", err)
+ }
+ return oldValue.SubscriptionDays, nil
+}
+
+// AddSubscriptionDays adds i to the "subscription_days" field.
+func (m *PaymentOrderMutation) AddSubscriptionDays(i int) {
+ if m.addsubscription_days != nil {
+ *m.addsubscription_days += i
+ } else {
+ m.addsubscription_days = &i
+ }
+}
+
+// AddedSubscriptionDays returns the value that was added to the "subscription_days" field in this mutation.
+func (m *PaymentOrderMutation) AddedSubscriptionDays() (r int, exists bool) {
+ v := m.addsubscription_days
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// ClearSubscriptionDays clears the value of the "subscription_days" field.
+func (m *PaymentOrderMutation) ClearSubscriptionDays() {
+ m.subscription_days = nil
+ m.addsubscription_days = nil
+ m.clearedFields[paymentorder.FieldSubscriptionDays] = struct{}{}
+}
+
+// SubscriptionDaysCleared returns if the "subscription_days" field was cleared in this mutation.
+func (m *PaymentOrderMutation) SubscriptionDaysCleared() bool {
+ _, ok := m.clearedFields[paymentorder.FieldSubscriptionDays]
+ return ok
+}
+
+// ResetSubscriptionDays resets all changes to the "subscription_days" field.
+func (m *PaymentOrderMutation) ResetSubscriptionDays() {
+ m.subscription_days = nil
+ m.addsubscription_days = nil
+ delete(m.clearedFields, paymentorder.FieldSubscriptionDays)
+}
+
+// SetProviderInstanceID sets the "provider_instance_id" field.
+func (m *PaymentOrderMutation) SetProviderInstanceID(s string) {
+ m.provider_instance_id = &s
+}
+
+// ProviderInstanceID returns the value of the "provider_instance_id" field in the mutation.
+func (m *PaymentOrderMutation) ProviderInstanceID() (r string, exists bool) {
+ v := m.provider_instance_id
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldProviderInstanceID returns the old "provider_instance_id" field's value of the PaymentOrder entity.
+// If the PaymentOrder object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PaymentOrderMutation) OldProviderInstanceID(ctx context.Context) (v *string, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldProviderInstanceID is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldProviderInstanceID requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldProviderInstanceID: %w", err)
+ }
+ return oldValue.ProviderInstanceID, nil
+}
+
+// ClearProviderInstanceID clears the value of the "provider_instance_id" field.
+func (m *PaymentOrderMutation) ClearProviderInstanceID() {
+ m.provider_instance_id = nil
+ m.clearedFields[paymentorder.FieldProviderInstanceID] = struct{}{}
+}
+
+// ProviderInstanceIDCleared returns if the "provider_instance_id" field was cleared in this mutation.
+func (m *PaymentOrderMutation) ProviderInstanceIDCleared() bool {
+ _, ok := m.clearedFields[paymentorder.FieldProviderInstanceID]
+ return ok
+}
+
+// ResetProviderInstanceID resets all changes to the "provider_instance_id" field.
+func (m *PaymentOrderMutation) ResetProviderInstanceID() {
+ m.provider_instance_id = nil
+ delete(m.clearedFields, paymentorder.FieldProviderInstanceID)
+}
+
+// SetStatus sets the "status" field.
+func (m *PaymentOrderMutation) SetStatus(s string) {
+ m.status = &s
+}
+
+// Status returns the value of the "status" field in the mutation.
+func (m *PaymentOrderMutation) Status() (r string, exists bool) {
+ v := m.status
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldStatus returns the old "status" field's value of the PaymentOrder entity.
+// If the PaymentOrder object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PaymentOrderMutation) OldStatus(ctx context.Context) (v string, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldStatus is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldStatus requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldStatus: %w", err)
+ }
+ return oldValue.Status, nil
+}
+
+// ResetStatus resets all changes to the "status" field.
+func (m *PaymentOrderMutation) ResetStatus() {
+ m.status = nil
+}
+
+// SetRefundAmount sets the "refund_amount" field.
+func (m *PaymentOrderMutation) SetRefundAmount(f float64) {
+ m.refund_amount = &f
+ m.addrefund_amount = nil
+}
+
+// RefundAmount returns the value of the "refund_amount" field in the mutation.
+func (m *PaymentOrderMutation) RefundAmount() (r float64, exists bool) {
+ v := m.refund_amount
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldRefundAmount returns the old "refund_amount" field's value of the PaymentOrder entity.
+// If the PaymentOrder object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PaymentOrderMutation) OldRefundAmount(ctx context.Context) (v float64, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldRefundAmount is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldRefundAmount requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldRefundAmount: %w", err)
+ }
+ return oldValue.RefundAmount, nil
+}
+
+// AddRefundAmount adds f to the "refund_amount" field.
+func (m *PaymentOrderMutation) AddRefundAmount(f float64) {
+ if m.addrefund_amount != nil {
+ *m.addrefund_amount += f
+ } else {
+ m.addrefund_amount = &f
+ }
+}
+
+// AddedRefundAmount returns the value that was added to the "refund_amount" field in this mutation.
+func (m *PaymentOrderMutation) AddedRefundAmount() (r float64, exists bool) {
+ v := m.addrefund_amount
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// ResetRefundAmount resets all changes to the "refund_amount" field.
+func (m *PaymentOrderMutation) ResetRefundAmount() {
+ m.refund_amount = nil
+ m.addrefund_amount = nil
+}
+
+// SetRefundReason sets the "refund_reason" field.
+func (m *PaymentOrderMutation) SetRefundReason(s string) {
+ m.refund_reason = &s
+}
+
+// RefundReason returns the value of the "refund_reason" field in the mutation.
+func (m *PaymentOrderMutation) RefundReason() (r string, exists bool) {
+ v := m.refund_reason
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldRefundReason returns the old "refund_reason" field's value of the PaymentOrder entity.
+// If the PaymentOrder object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PaymentOrderMutation) OldRefundReason(ctx context.Context) (v *string, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldRefundReason is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldRefundReason requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldRefundReason: %w", err)
+ }
+ return oldValue.RefundReason, nil
+}
+
+// ClearRefundReason clears the value of the "refund_reason" field.
+func (m *PaymentOrderMutation) ClearRefundReason() {
+ m.refund_reason = nil
+ m.clearedFields[paymentorder.FieldRefundReason] = struct{}{}
+}
+
+// RefundReasonCleared returns if the "refund_reason" field was cleared in this mutation.
+func (m *PaymentOrderMutation) RefundReasonCleared() bool {
+ _, ok := m.clearedFields[paymentorder.FieldRefundReason]
+ return ok
+}
+
+// ResetRefundReason resets all changes to the "refund_reason" field.
+func (m *PaymentOrderMutation) ResetRefundReason() {
+ m.refund_reason = nil
+ delete(m.clearedFields, paymentorder.FieldRefundReason)
+}
+
+// SetRefundAt sets the "refund_at" field.
+func (m *PaymentOrderMutation) SetRefundAt(t time.Time) {
+ m.refund_at = &t
+}
+
+// RefundAt returns the value of the "refund_at" field in the mutation.
+func (m *PaymentOrderMutation) RefundAt() (r time.Time, exists bool) {
+ v := m.refund_at
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldRefundAt returns the old "refund_at" field's value of the PaymentOrder entity.
+// If the PaymentOrder object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PaymentOrderMutation) OldRefundAt(ctx context.Context) (v *time.Time, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldRefundAt is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldRefundAt requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldRefundAt: %w", err)
+ }
+ return oldValue.RefundAt, nil
+}
+
+// ClearRefundAt clears the value of the "refund_at" field.
+func (m *PaymentOrderMutation) ClearRefundAt() {
+ m.refund_at = nil
+ m.clearedFields[paymentorder.FieldRefundAt] = struct{}{}
+}
+
+// RefundAtCleared returns if the "refund_at" field was cleared in this mutation.
+func (m *PaymentOrderMutation) RefundAtCleared() bool {
+ _, ok := m.clearedFields[paymentorder.FieldRefundAt]
+ return ok
+}
+
+// ResetRefundAt resets all changes to the "refund_at" field.
+func (m *PaymentOrderMutation) ResetRefundAt() {
+ m.refund_at = nil
+ delete(m.clearedFields, paymentorder.FieldRefundAt)
+}
+
+// SetForceRefund sets the "force_refund" field.
+func (m *PaymentOrderMutation) SetForceRefund(b bool) {
+ m.force_refund = &b
+}
+
+// ForceRefund returns the value of the "force_refund" field in the mutation.
+func (m *PaymentOrderMutation) ForceRefund() (r bool, exists bool) {
+ v := m.force_refund
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldForceRefund returns the old "force_refund" field's value of the PaymentOrder entity.
+// If the PaymentOrder object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PaymentOrderMutation) OldForceRefund(ctx context.Context) (v bool, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldForceRefund is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldForceRefund requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldForceRefund: %w", err)
+ }
+ return oldValue.ForceRefund, nil
+}
+
+// ResetForceRefund resets all changes to the "force_refund" field.
+func (m *PaymentOrderMutation) ResetForceRefund() {
+ m.force_refund = nil
+}
+
+// SetRefundRequestedAt sets the "refund_requested_at" field.
+func (m *PaymentOrderMutation) SetRefundRequestedAt(t time.Time) {
+ m.refund_requested_at = &t
+}
+
+// RefundRequestedAt returns the value of the "refund_requested_at" field in the mutation.
+func (m *PaymentOrderMutation) RefundRequestedAt() (r time.Time, exists bool) {
+ v := m.refund_requested_at
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldRefundRequestedAt returns the old "refund_requested_at" field's value of the PaymentOrder entity.
+// If the PaymentOrder object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PaymentOrderMutation) OldRefundRequestedAt(ctx context.Context) (v *time.Time, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldRefundRequestedAt is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldRefundRequestedAt requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldRefundRequestedAt: %w", err)
+ }
+ return oldValue.RefundRequestedAt, nil
+}
+
+// ClearRefundRequestedAt clears the value of the "refund_requested_at" field.
+func (m *PaymentOrderMutation) ClearRefundRequestedAt() {
+ m.refund_requested_at = nil
+ m.clearedFields[paymentorder.FieldRefundRequestedAt] = struct{}{}
+}
+
+// RefundRequestedAtCleared returns if the "refund_requested_at" field was cleared in this mutation.
+func (m *PaymentOrderMutation) RefundRequestedAtCleared() bool {
+ _, ok := m.clearedFields[paymentorder.FieldRefundRequestedAt]
+ return ok
+}
+
+// ResetRefundRequestedAt resets all changes to the "refund_requested_at" field.
+func (m *PaymentOrderMutation) ResetRefundRequestedAt() {
+ m.refund_requested_at = nil
+ delete(m.clearedFields, paymentorder.FieldRefundRequestedAt)
+}
+
+// SetRefundRequestReason sets the "refund_request_reason" field.
+func (m *PaymentOrderMutation) SetRefundRequestReason(s string) {
+ m.refund_request_reason = &s
+}
+
+// RefundRequestReason returns the value of the "refund_request_reason" field in the mutation.
+func (m *PaymentOrderMutation) RefundRequestReason() (r string, exists bool) {
+ v := m.refund_request_reason
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldRefundRequestReason returns the old "refund_request_reason" field's value of the PaymentOrder entity.
+// If the PaymentOrder object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PaymentOrderMutation) OldRefundRequestReason(ctx context.Context) (v *string, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldRefundRequestReason is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldRefundRequestReason requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldRefundRequestReason: %w", err)
+ }
+ return oldValue.RefundRequestReason, nil
+}
+
+// ClearRefundRequestReason clears the value of the "refund_request_reason" field.
+func (m *PaymentOrderMutation) ClearRefundRequestReason() {
+ m.refund_request_reason = nil
+ m.clearedFields[paymentorder.FieldRefundRequestReason] = struct{}{}
+}
+
+// RefundRequestReasonCleared returns if the "refund_request_reason" field was cleared in this mutation.
+func (m *PaymentOrderMutation) RefundRequestReasonCleared() bool {
+ _, ok := m.clearedFields[paymentorder.FieldRefundRequestReason]
+ return ok
+}
+
+// ResetRefundRequestReason resets all changes to the "refund_request_reason" field.
+func (m *PaymentOrderMutation) ResetRefundRequestReason() {
+ m.refund_request_reason = nil
+ delete(m.clearedFields, paymentorder.FieldRefundRequestReason)
+}
+
+// SetRefundRequestedBy sets the "refund_requested_by" field.
+func (m *PaymentOrderMutation) SetRefundRequestedBy(s string) {
+ m.refund_requested_by = &s
+}
+
+// RefundRequestedBy returns the value of the "refund_requested_by" field in the mutation.
+func (m *PaymentOrderMutation) RefundRequestedBy() (r string, exists bool) {
+ v := m.refund_requested_by
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldRefundRequestedBy returns the old "refund_requested_by" field's value of the PaymentOrder entity.
+// If the PaymentOrder object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PaymentOrderMutation) OldRefundRequestedBy(ctx context.Context) (v *string, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldRefundRequestedBy is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldRefundRequestedBy requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldRefundRequestedBy: %w", err)
+ }
+ return oldValue.RefundRequestedBy, nil
+}
+
+// ClearRefundRequestedBy clears the value of the "refund_requested_by" field.
+func (m *PaymentOrderMutation) ClearRefundRequestedBy() {
+ m.refund_requested_by = nil
+ m.clearedFields[paymentorder.FieldRefundRequestedBy] = struct{}{}
+}
+
+// RefundRequestedByCleared returns if the "refund_requested_by" field was cleared in this mutation.
+func (m *PaymentOrderMutation) RefundRequestedByCleared() bool {
+ _, ok := m.clearedFields[paymentorder.FieldRefundRequestedBy]
+ return ok
+}
+
+// ResetRefundRequestedBy resets all changes to the "refund_requested_by" field.
+func (m *PaymentOrderMutation) ResetRefundRequestedBy() {
+ m.refund_requested_by = nil
+ delete(m.clearedFields, paymentorder.FieldRefundRequestedBy)
+}
+
+// SetExpiresAt sets the "expires_at" field.
+func (m *PaymentOrderMutation) SetExpiresAt(t time.Time) {
+ m.expires_at = &t
+}
+
+// ExpiresAt returns the value of the "expires_at" field in the mutation.
+func (m *PaymentOrderMutation) ExpiresAt() (r time.Time, exists bool) {
+ v := m.expires_at
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldExpiresAt returns the old "expires_at" field's value of the PaymentOrder entity.
+// If the PaymentOrder object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PaymentOrderMutation) OldExpiresAt(ctx context.Context) (v time.Time, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldExpiresAt is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldExpiresAt requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldExpiresAt: %w", err)
+ }
+ return oldValue.ExpiresAt, nil
+}
+
+// ResetExpiresAt resets all changes to the "expires_at" field.
+func (m *PaymentOrderMutation) ResetExpiresAt() {
+ m.expires_at = nil
+}
+
+// SetPaidAt sets the "paid_at" field.
+func (m *PaymentOrderMutation) SetPaidAt(t time.Time) {
+ m.paid_at = &t
+}
+
+// PaidAt returns the value of the "paid_at" field in the mutation.
+func (m *PaymentOrderMutation) PaidAt() (r time.Time, exists bool) {
+ v := m.paid_at
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldPaidAt returns the old "paid_at" field's value of the PaymentOrder entity.
+// If the PaymentOrder object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PaymentOrderMutation) OldPaidAt(ctx context.Context) (v *time.Time, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldPaidAt is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldPaidAt requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldPaidAt: %w", err)
+ }
+ return oldValue.PaidAt, nil
+}
+
+// ClearPaidAt clears the value of the "paid_at" field.
+func (m *PaymentOrderMutation) ClearPaidAt() {
+ m.paid_at = nil
+ m.clearedFields[paymentorder.FieldPaidAt] = struct{}{}
+}
+
+// PaidAtCleared returns if the "paid_at" field was cleared in this mutation.
+func (m *PaymentOrderMutation) PaidAtCleared() bool {
+ _, ok := m.clearedFields[paymentorder.FieldPaidAt]
+ return ok
+}
+
+// ResetPaidAt resets all changes to the "paid_at" field.
+func (m *PaymentOrderMutation) ResetPaidAt() {
+ m.paid_at = nil
+ delete(m.clearedFields, paymentorder.FieldPaidAt)
+}
+
+// SetCompletedAt sets the "completed_at" field.
+func (m *PaymentOrderMutation) SetCompletedAt(t time.Time) {
+ m.completed_at = &t
+}
+
+// CompletedAt returns the value of the "completed_at" field in the mutation.
+func (m *PaymentOrderMutation) CompletedAt() (r time.Time, exists bool) {
+ v := m.completed_at
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldCompletedAt returns the old "completed_at" field's value of the PaymentOrder entity.
+// If the PaymentOrder object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PaymentOrderMutation) OldCompletedAt(ctx context.Context) (v *time.Time, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldCompletedAt is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldCompletedAt requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldCompletedAt: %w", err)
+ }
+ return oldValue.CompletedAt, nil
+}
+
+// ClearCompletedAt clears the value of the "completed_at" field.
+func (m *PaymentOrderMutation) ClearCompletedAt() {
+ m.completed_at = nil
+ m.clearedFields[paymentorder.FieldCompletedAt] = struct{}{}
+}
+
+// CompletedAtCleared returns if the "completed_at" field was cleared in this mutation.
+func (m *PaymentOrderMutation) CompletedAtCleared() bool {
+ _, ok := m.clearedFields[paymentorder.FieldCompletedAt]
+ return ok
+}
+
+// ResetCompletedAt resets all changes to the "completed_at" field.
+func (m *PaymentOrderMutation) ResetCompletedAt() {
+ m.completed_at = nil
+ delete(m.clearedFields, paymentorder.FieldCompletedAt)
+}
+
+// SetFailedAt sets the "failed_at" field.
+func (m *PaymentOrderMutation) SetFailedAt(t time.Time) {
+ m.failed_at = &t
+}
+
+// FailedAt returns the value of the "failed_at" field in the mutation.
+func (m *PaymentOrderMutation) FailedAt() (r time.Time, exists bool) {
+ v := m.failed_at
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldFailedAt returns the old "failed_at" field's value of the PaymentOrder entity.
+// If the PaymentOrder object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PaymentOrderMutation) OldFailedAt(ctx context.Context) (v *time.Time, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldFailedAt is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldFailedAt requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldFailedAt: %w", err)
+ }
+ return oldValue.FailedAt, nil
+}
+
+// ClearFailedAt clears the value of the "failed_at" field.
+func (m *PaymentOrderMutation) ClearFailedAt() {
+ m.failed_at = nil
+ m.clearedFields[paymentorder.FieldFailedAt] = struct{}{}
+}
+
+// FailedAtCleared returns if the "failed_at" field was cleared in this mutation.
+func (m *PaymentOrderMutation) FailedAtCleared() bool {
+ _, ok := m.clearedFields[paymentorder.FieldFailedAt]
+ return ok
+}
+
+// ResetFailedAt resets all changes to the "failed_at" field.
+func (m *PaymentOrderMutation) ResetFailedAt() {
+ m.failed_at = nil
+ delete(m.clearedFields, paymentorder.FieldFailedAt)
+}
+
+// SetFailedReason sets the "failed_reason" field.
+func (m *PaymentOrderMutation) SetFailedReason(s string) {
+ m.failed_reason = &s
+}
+
+// FailedReason returns the value of the "failed_reason" field in the mutation.
+func (m *PaymentOrderMutation) FailedReason() (r string, exists bool) {
+ v := m.failed_reason
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldFailedReason returns the old "failed_reason" field's value of the PaymentOrder entity.
+// If the PaymentOrder object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PaymentOrderMutation) OldFailedReason(ctx context.Context) (v *string, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldFailedReason is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldFailedReason requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldFailedReason: %w", err)
+ }
+ return oldValue.FailedReason, nil
+}
+
+// ClearFailedReason clears the value of the "failed_reason" field.
+func (m *PaymentOrderMutation) ClearFailedReason() {
+ m.failed_reason = nil
+ m.clearedFields[paymentorder.FieldFailedReason] = struct{}{}
+}
+
+// FailedReasonCleared returns if the "failed_reason" field was cleared in this mutation.
+func (m *PaymentOrderMutation) FailedReasonCleared() bool {
+ _, ok := m.clearedFields[paymentorder.FieldFailedReason]
+ return ok
+}
+
+// ResetFailedReason resets all changes to the "failed_reason" field.
+func (m *PaymentOrderMutation) ResetFailedReason() {
+ m.failed_reason = nil
+ delete(m.clearedFields, paymentorder.FieldFailedReason)
+}
+
+// SetClientIP sets the "client_ip" field.
+func (m *PaymentOrderMutation) SetClientIP(s string) {
+ m.client_ip = &s
+}
+
+// ClientIP returns the value of the "client_ip" field in the mutation.
+func (m *PaymentOrderMutation) ClientIP() (r string, exists bool) {
+ v := m.client_ip
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldClientIP returns the old "client_ip" field's value of the PaymentOrder entity.
+// If the PaymentOrder object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PaymentOrderMutation) OldClientIP(ctx context.Context) (v string, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldClientIP is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldClientIP requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldClientIP: %w", err)
+ }
+ return oldValue.ClientIP, nil
+}
+
+// ResetClientIP resets all changes to the "client_ip" field.
+func (m *PaymentOrderMutation) ResetClientIP() {
+ m.client_ip = nil
+}
+
+// SetSrcHost sets the "src_host" field.
+func (m *PaymentOrderMutation) SetSrcHost(s string) {
+ m.src_host = &s
+}
+
+// SrcHost returns the value of the "src_host" field in the mutation.
+func (m *PaymentOrderMutation) SrcHost() (r string, exists bool) {
+ v := m.src_host
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldSrcHost returns the old "src_host" field's value of the PaymentOrder entity.
+// If the PaymentOrder object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PaymentOrderMutation) OldSrcHost(ctx context.Context) (v string, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldSrcHost is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldSrcHost requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldSrcHost: %w", err)
+ }
+ return oldValue.SrcHost, nil
+}
+
+// ResetSrcHost resets all changes to the "src_host" field.
+func (m *PaymentOrderMutation) ResetSrcHost() {
+ m.src_host = nil
+}
+
+// SetSrcURL sets the "src_url" field.
+func (m *PaymentOrderMutation) SetSrcURL(s string) {
+ m.src_url = &s
+}
+
+// SrcURL returns the value of the "src_url" field in the mutation.
+func (m *PaymentOrderMutation) SrcURL() (r string, exists bool) {
+ v := m.src_url
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldSrcURL returns the old "src_url" field's value of the PaymentOrder entity.
+// If the PaymentOrder object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PaymentOrderMutation) OldSrcURL(ctx context.Context) (v *string, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldSrcURL is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldSrcURL requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldSrcURL: %w", err)
+ }
+ return oldValue.SrcURL, nil
+}
+
+// ClearSrcURL clears the value of the "src_url" field.
+func (m *PaymentOrderMutation) ClearSrcURL() {
+ m.src_url = nil
+ m.clearedFields[paymentorder.FieldSrcURL] = struct{}{}
+}
+
+// SrcURLCleared returns if the "src_url" field was cleared in this mutation.
+func (m *PaymentOrderMutation) SrcURLCleared() bool {
+ _, ok := m.clearedFields[paymentorder.FieldSrcURL]
+ return ok
+}
+
+// ResetSrcURL resets all changes to the "src_url" field.
+func (m *PaymentOrderMutation) ResetSrcURL() {
+ m.src_url = nil
+ delete(m.clearedFields, paymentorder.FieldSrcURL)
+}
+
+// SetCreatedAt sets the "created_at" field.
+func (m *PaymentOrderMutation) SetCreatedAt(t time.Time) {
+ m.created_at = &t
+}
+
+// CreatedAt returns the value of the "created_at" field in the mutation.
+func (m *PaymentOrderMutation) CreatedAt() (r time.Time, exists bool) {
+ v := m.created_at
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldCreatedAt returns the old "created_at" field's value of the PaymentOrder entity.
+// If the PaymentOrder object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PaymentOrderMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldCreatedAt requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err)
+ }
+ return oldValue.CreatedAt, nil
+}
+
+// ResetCreatedAt resets all changes to the "created_at" field.
+func (m *PaymentOrderMutation) ResetCreatedAt() {
+ m.created_at = nil
+}
+
+// SetUpdatedAt sets the "updated_at" field.
+func (m *PaymentOrderMutation) SetUpdatedAt(t time.Time) {
+ m.updated_at = &t
+}
+
+// UpdatedAt returns the value of the "updated_at" field in the mutation.
+func (m *PaymentOrderMutation) UpdatedAt() (r time.Time, exists bool) {
+ v := m.updated_at
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldUpdatedAt returns the old "updated_at" field's value of the PaymentOrder entity.
+// If the PaymentOrder object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PaymentOrderMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldUpdatedAt requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err)
+ }
+ return oldValue.UpdatedAt, nil
+}
+
+// ResetUpdatedAt resets all changes to the "updated_at" field.
+func (m *PaymentOrderMutation) ResetUpdatedAt() {
+ m.updated_at = nil
+}
+
+// ClearUser clears the "user" edge to the User entity.
+func (m *PaymentOrderMutation) ClearUser() {
+ m.cleareduser = true
+ m.clearedFields[paymentorder.FieldUserID] = struct{}{}
+}
+
+// UserCleared reports if the "user" edge to the User entity was cleared.
+func (m *PaymentOrderMutation) UserCleared() bool {
+ return m.cleareduser
+}
+
+// UserIDs returns the "user" edge IDs in the mutation.
+// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use
+// UserID instead. It exists only for internal usage by the builders.
+func (m *PaymentOrderMutation) UserIDs() (ids []int64) {
+ if id := m.user; id != nil {
+ ids = append(ids, *id)
+ }
+ return
+}
+
+// ResetUser resets all changes to the "user" edge.
+func (m *PaymentOrderMutation) ResetUser() {
+ m.user = nil
+ m.cleareduser = false
+}
+
+// Where appends a list predicates to the PaymentOrderMutation builder.
+func (m *PaymentOrderMutation) Where(ps ...predicate.PaymentOrder) {
+ m.predicates = append(m.predicates, ps...)
+}
+
+// WhereP appends storage-level predicates to the PaymentOrderMutation builder. Using this method,
+// users can use type-assertion to append predicates that do not depend on any generated package.
+func (m *PaymentOrderMutation) WhereP(ps ...func(*sql.Selector)) {
+ p := make([]predicate.PaymentOrder, len(ps))
+ for i := range ps {
+ p[i] = ps[i]
+ }
+ m.Where(p...)
+}
+
+// Op returns the operation name.
+func (m *PaymentOrderMutation) Op() Op {
+ return m.op
+}
+
+// SetOp allows setting the mutation operation.
+func (m *PaymentOrderMutation) SetOp(op Op) {
+ m.op = op
+}
+
+// Type returns the node type of this mutation (PaymentOrder).
+func (m *PaymentOrderMutation) Type() string {
+ return m.typ
+}
+
+// Fields returns all fields that were changed during this mutation. Note that in
+// order to get all numeric fields that were incremented/decremented, call
+// AddedFields().
+func (m *PaymentOrderMutation) Fields() []string {
+ fields := make([]string, 0, 37)
+ if m.user != nil {
+ fields = append(fields, paymentorder.FieldUserID)
+ }
+ if m.user_email != nil {
+ fields = append(fields, paymentorder.FieldUserEmail)
+ }
+ if m.user_name != nil {
+ fields = append(fields, paymentorder.FieldUserName)
+ }
+ if m.user_notes != nil {
+ fields = append(fields, paymentorder.FieldUserNotes)
+ }
+ if m.amount != nil {
+ fields = append(fields, paymentorder.FieldAmount)
+ }
+ if m.pay_amount != nil {
+ fields = append(fields, paymentorder.FieldPayAmount)
+ }
+ if m.fee_rate != nil {
+ fields = append(fields, paymentorder.FieldFeeRate)
+ }
+ if m.recharge_code != nil {
+ fields = append(fields, paymentorder.FieldRechargeCode)
+ }
+ if m.out_trade_no != nil {
+ fields = append(fields, paymentorder.FieldOutTradeNo)
+ }
+ if m.payment_type != nil {
+ fields = append(fields, paymentorder.FieldPaymentType)
+ }
+ if m.payment_trade_no != nil {
+ fields = append(fields, paymentorder.FieldPaymentTradeNo)
+ }
+ if m.pay_url != nil {
+ fields = append(fields, paymentorder.FieldPayURL)
+ }
+ if m.qr_code != nil {
+ fields = append(fields, paymentorder.FieldQrCode)
+ }
+ if m.qr_code_img != nil {
+ fields = append(fields, paymentorder.FieldQrCodeImg)
+ }
+ if m.order_type != nil {
+ fields = append(fields, paymentorder.FieldOrderType)
+ }
+ if m.plan_id != nil {
+ fields = append(fields, paymentorder.FieldPlanID)
+ }
+ if m.subscription_group_id != nil {
+ fields = append(fields, paymentorder.FieldSubscriptionGroupID)
+ }
+ if m.subscription_days != nil {
+ fields = append(fields, paymentorder.FieldSubscriptionDays)
+ }
+ if m.provider_instance_id != nil {
+ fields = append(fields, paymentorder.FieldProviderInstanceID)
+ }
+ if m.status != nil {
+ fields = append(fields, paymentorder.FieldStatus)
+ }
+ if m.refund_amount != nil {
+ fields = append(fields, paymentorder.FieldRefundAmount)
+ }
+ if m.refund_reason != nil {
+ fields = append(fields, paymentorder.FieldRefundReason)
+ }
+ if m.refund_at != nil {
+ fields = append(fields, paymentorder.FieldRefundAt)
+ }
+ if m.force_refund != nil {
+ fields = append(fields, paymentorder.FieldForceRefund)
+ }
+ if m.refund_requested_at != nil {
+ fields = append(fields, paymentorder.FieldRefundRequestedAt)
+ }
+ if m.refund_request_reason != nil {
+ fields = append(fields, paymentorder.FieldRefundRequestReason)
+ }
+ if m.refund_requested_by != nil {
+ fields = append(fields, paymentorder.FieldRefundRequestedBy)
+ }
+ if m.expires_at != nil {
+ fields = append(fields, paymentorder.FieldExpiresAt)
+ }
+ if m.paid_at != nil {
+ fields = append(fields, paymentorder.FieldPaidAt)
+ }
+ if m.completed_at != nil {
+ fields = append(fields, paymentorder.FieldCompletedAt)
+ }
+ if m.failed_at != nil {
+ fields = append(fields, paymentorder.FieldFailedAt)
+ }
+ if m.failed_reason != nil {
+ fields = append(fields, paymentorder.FieldFailedReason)
+ }
+ if m.client_ip != nil {
+ fields = append(fields, paymentorder.FieldClientIP)
+ }
+ if m.src_host != nil {
+ fields = append(fields, paymentorder.FieldSrcHost)
+ }
+ if m.src_url != nil {
+ fields = append(fields, paymentorder.FieldSrcURL)
+ }
+ if m.created_at != nil {
+ fields = append(fields, paymentorder.FieldCreatedAt)
+ }
+ if m.updated_at != nil {
+ fields = append(fields, paymentorder.FieldUpdatedAt)
+ }
+ return fields
+}
+
+// Field returns the value of a field with the given name. The second boolean
+// return value indicates that this field was not set, or was not defined in the
+// schema.
+func (m *PaymentOrderMutation) Field(name string) (ent.Value, bool) {
+ switch name {
+ case paymentorder.FieldUserID:
+ return m.UserID()
+ case paymentorder.FieldUserEmail:
+ return m.UserEmail()
+ case paymentorder.FieldUserName:
+ return m.UserName()
+ case paymentorder.FieldUserNotes:
+ return m.UserNotes()
+ case paymentorder.FieldAmount:
+ return m.Amount()
+ case paymentorder.FieldPayAmount:
+ return m.PayAmount()
+ case paymentorder.FieldFeeRate:
+ return m.FeeRate()
+ case paymentorder.FieldRechargeCode:
+ return m.RechargeCode()
+ case paymentorder.FieldOutTradeNo:
+ return m.OutTradeNo()
+ case paymentorder.FieldPaymentType:
+ return m.PaymentType()
+ case paymentorder.FieldPaymentTradeNo:
+ return m.PaymentTradeNo()
+ case paymentorder.FieldPayURL:
+ return m.PayURL()
+ case paymentorder.FieldQrCode:
+ return m.QrCode()
+ case paymentorder.FieldQrCodeImg:
+ return m.QrCodeImg()
+ case paymentorder.FieldOrderType:
+ return m.OrderType()
+ case paymentorder.FieldPlanID:
+ return m.PlanID()
+ case paymentorder.FieldSubscriptionGroupID:
+ return m.SubscriptionGroupID()
+ case paymentorder.FieldSubscriptionDays:
+ return m.SubscriptionDays()
+ case paymentorder.FieldProviderInstanceID:
+ return m.ProviderInstanceID()
+ case paymentorder.FieldStatus:
+ return m.Status()
+ case paymentorder.FieldRefundAmount:
+ return m.RefundAmount()
+ case paymentorder.FieldRefundReason:
+ return m.RefundReason()
+ case paymentorder.FieldRefundAt:
+ return m.RefundAt()
+ case paymentorder.FieldForceRefund:
+ return m.ForceRefund()
+ case paymentorder.FieldRefundRequestedAt:
+ return m.RefundRequestedAt()
+ case paymentorder.FieldRefundRequestReason:
+ return m.RefundRequestReason()
+ case paymentorder.FieldRefundRequestedBy:
+ return m.RefundRequestedBy()
+ case paymentorder.FieldExpiresAt:
+ return m.ExpiresAt()
+ case paymentorder.FieldPaidAt:
+ return m.PaidAt()
+ case paymentorder.FieldCompletedAt:
+ return m.CompletedAt()
+ case paymentorder.FieldFailedAt:
+ return m.FailedAt()
+ case paymentorder.FieldFailedReason:
+ return m.FailedReason()
+ case paymentorder.FieldClientIP:
+ return m.ClientIP()
+ case paymentorder.FieldSrcHost:
+ return m.SrcHost()
+ case paymentorder.FieldSrcURL:
+ return m.SrcURL()
+ case paymentorder.FieldCreatedAt:
+ return m.CreatedAt()
+ case paymentorder.FieldUpdatedAt:
+ return m.UpdatedAt()
+ }
+ return nil, false
+}
+
+// OldField returns the old value of the field from the database. An error is
+// returned if the mutation operation is not UpdateOne, or the query to the
+// database failed.
+func (m *PaymentOrderMutation) OldField(ctx context.Context, name string) (ent.Value, error) {
+ switch name {
+ case paymentorder.FieldUserID:
+ return m.OldUserID(ctx)
+ case paymentorder.FieldUserEmail:
+ return m.OldUserEmail(ctx)
+ case paymentorder.FieldUserName:
+ return m.OldUserName(ctx)
+ case paymentorder.FieldUserNotes:
+ return m.OldUserNotes(ctx)
+ case paymentorder.FieldAmount:
+ return m.OldAmount(ctx)
+ case paymentorder.FieldPayAmount:
+ return m.OldPayAmount(ctx)
+ case paymentorder.FieldFeeRate:
+ return m.OldFeeRate(ctx)
+ case paymentorder.FieldRechargeCode:
+ return m.OldRechargeCode(ctx)
+ case paymentorder.FieldOutTradeNo:
+ return m.OldOutTradeNo(ctx)
+ case paymentorder.FieldPaymentType:
+ return m.OldPaymentType(ctx)
+ case paymentorder.FieldPaymentTradeNo:
+ return m.OldPaymentTradeNo(ctx)
+ case paymentorder.FieldPayURL:
+ return m.OldPayURL(ctx)
+ case paymentorder.FieldQrCode:
+ return m.OldQrCode(ctx)
+ case paymentorder.FieldQrCodeImg:
+ return m.OldQrCodeImg(ctx)
+ case paymentorder.FieldOrderType:
+ return m.OldOrderType(ctx)
+ case paymentorder.FieldPlanID:
+ return m.OldPlanID(ctx)
+ case paymentorder.FieldSubscriptionGroupID:
+ return m.OldSubscriptionGroupID(ctx)
+ case paymentorder.FieldSubscriptionDays:
+ return m.OldSubscriptionDays(ctx)
+ case paymentorder.FieldProviderInstanceID:
+ return m.OldProviderInstanceID(ctx)
+ case paymentorder.FieldStatus:
+ return m.OldStatus(ctx)
+ case paymentorder.FieldRefundAmount:
+ return m.OldRefundAmount(ctx)
+ case paymentorder.FieldRefundReason:
+ return m.OldRefundReason(ctx)
+ case paymentorder.FieldRefundAt:
+ return m.OldRefundAt(ctx)
+ case paymentorder.FieldForceRefund:
+ return m.OldForceRefund(ctx)
+ case paymentorder.FieldRefundRequestedAt:
+ return m.OldRefundRequestedAt(ctx)
+ case paymentorder.FieldRefundRequestReason:
+ return m.OldRefundRequestReason(ctx)
+ case paymentorder.FieldRefundRequestedBy:
+ return m.OldRefundRequestedBy(ctx)
+ case paymentorder.FieldExpiresAt:
+ return m.OldExpiresAt(ctx)
+ case paymentorder.FieldPaidAt:
+ return m.OldPaidAt(ctx)
+ case paymentorder.FieldCompletedAt:
+ return m.OldCompletedAt(ctx)
+ case paymentorder.FieldFailedAt:
+ return m.OldFailedAt(ctx)
+ case paymentorder.FieldFailedReason:
+ return m.OldFailedReason(ctx)
+ case paymentorder.FieldClientIP:
+ return m.OldClientIP(ctx)
+ case paymentorder.FieldSrcHost:
+ return m.OldSrcHost(ctx)
+ case paymentorder.FieldSrcURL:
+ return m.OldSrcURL(ctx)
+ case paymentorder.FieldCreatedAt:
+ return m.OldCreatedAt(ctx)
+ case paymentorder.FieldUpdatedAt:
+ return m.OldUpdatedAt(ctx)
+ }
+ return nil, fmt.Errorf("unknown PaymentOrder field %s", name)
+}
+
+// SetField sets the value of a field with the given name. It returns an error if
+// the field is not defined in the schema, or if the type mismatched the field
+// type.
+func (m *PaymentOrderMutation) SetField(name string, value ent.Value) error {
+ switch name {
+ case paymentorder.FieldUserID:
+ v, ok := value.(int64)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetUserID(v)
+ return nil
+ case paymentorder.FieldUserEmail:
+ v, ok := value.(string)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetUserEmail(v)
+ return nil
+ case paymentorder.FieldUserName:
+ v, ok := value.(string)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetUserName(v)
+ return nil
+ case paymentorder.FieldUserNotes:
+ v, ok := value.(string)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetUserNotes(v)
+ return nil
+ case paymentorder.FieldAmount:
+ v, ok := value.(float64)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetAmount(v)
+ return nil
+ case paymentorder.FieldPayAmount:
+ v, ok := value.(float64)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetPayAmount(v)
+ return nil
+ case paymentorder.FieldFeeRate:
+ v, ok := value.(float64)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetFeeRate(v)
+ return nil
+ case paymentorder.FieldRechargeCode:
+ v, ok := value.(string)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetRechargeCode(v)
+ return nil
+ case paymentorder.FieldOutTradeNo:
+ v, ok := value.(string)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetOutTradeNo(v)
+ return nil
+ case paymentorder.FieldPaymentType:
+ v, ok := value.(string)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetPaymentType(v)
+ return nil
+ case paymentorder.FieldPaymentTradeNo:
+ v, ok := value.(string)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetPaymentTradeNo(v)
+ return nil
+ case paymentorder.FieldPayURL:
+ v, ok := value.(string)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetPayURL(v)
+ return nil
+ case paymentorder.FieldQrCode:
+ v, ok := value.(string)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetQrCode(v)
+ return nil
+ case paymentorder.FieldQrCodeImg:
+ v, ok := value.(string)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetQrCodeImg(v)
+ return nil
+ case paymentorder.FieldOrderType:
+ v, ok := value.(string)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetOrderType(v)
+ return nil
+ case paymentorder.FieldPlanID:
+ v, ok := value.(int64)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetPlanID(v)
+ return nil
+ case paymentorder.FieldSubscriptionGroupID:
+ v, ok := value.(int64)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetSubscriptionGroupID(v)
+ return nil
+ case paymentorder.FieldSubscriptionDays:
+ v, ok := value.(int)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetSubscriptionDays(v)
+ return nil
+ case paymentorder.FieldProviderInstanceID:
+ v, ok := value.(string)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetProviderInstanceID(v)
+ return nil
+ case paymentorder.FieldStatus:
+ v, ok := value.(string)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetStatus(v)
+ return nil
+ case paymentorder.FieldRefundAmount:
+ v, ok := value.(float64)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetRefundAmount(v)
+ return nil
+ case paymentorder.FieldRefundReason:
+ v, ok := value.(string)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetRefundReason(v)
+ return nil
+ case paymentorder.FieldRefundAt:
+ v, ok := value.(time.Time)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetRefundAt(v)
+ return nil
+ case paymentorder.FieldForceRefund:
+ v, ok := value.(bool)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetForceRefund(v)
+ return nil
+ case paymentorder.FieldRefundRequestedAt:
+ v, ok := value.(time.Time)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetRefundRequestedAt(v)
+ return nil
+ case paymentorder.FieldRefundRequestReason:
+ v, ok := value.(string)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetRefundRequestReason(v)
+ return nil
+ case paymentorder.FieldRefundRequestedBy:
+ v, ok := value.(string)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetRefundRequestedBy(v)
+ return nil
+ case paymentorder.FieldExpiresAt:
+ v, ok := value.(time.Time)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetExpiresAt(v)
+ return nil
+ case paymentorder.FieldPaidAt:
+ v, ok := value.(time.Time)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetPaidAt(v)
+ return nil
+ case paymentorder.FieldCompletedAt:
+ v, ok := value.(time.Time)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetCompletedAt(v)
+ return nil
+ case paymentorder.FieldFailedAt:
+ v, ok := value.(time.Time)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetFailedAt(v)
+ return nil
+ case paymentorder.FieldFailedReason:
+ v, ok := value.(string)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetFailedReason(v)
+ return nil
+ case paymentorder.FieldClientIP:
+ v, ok := value.(string)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetClientIP(v)
+ return nil
+ case paymentorder.FieldSrcHost:
+ v, ok := value.(string)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetSrcHost(v)
+ return nil
+ case paymentorder.FieldSrcURL:
+ v, ok := value.(string)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetSrcURL(v)
+ return nil
+ case paymentorder.FieldCreatedAt:
+ v, ok := value.(time.Time)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetCreatedAt(v)
+ return nil
+ case paymentorder.FieldUpdatedAt:
+ v, ok := value.(time.Time)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetUpdatedAt(v)
+ return nil
+ }
+ return fmt.Errorf("unknown PaymentOrder field %s", name)
+}
+
+// AddedFields returns all numeric fields that were incremented/decremented during
+// this mutation.
+func (m *PaymentOrderMutation) AddedFields() []string {
+ var fields []string
+ if m.addamount != nil {
+ fields = append(fields, paymentorder.FieldAmount)
+ }
+ if m.addpay_amount != nil {
+ fields = append(fields, paymentorder.FieldPayAmount)
+ }
+ if m.addfee_rate != nil {
+ fields = append(fields, paymentorder.FieldFeeRate)
+ }
+ if m.addplan_id != nil {
+ fields = append(fields, paymentorder.FieldPlanID)
+ }
+ if m.addsubscription_group_id != nil {
+ fields = append(fields, paymentorder.FieldSubscriptionGroupID)
+ }
+ if m.addsubscription_days != nil {
+ fields = append(fields, paymentorder.FieldSubscriptionDays)
+ }
+ if m.addrefund_amount != nil {
+ fields = append(fields, paymentorder.FieldRefundAmount)
+ }
+ return fields
+}
+
+// AddedField returns the numeric value that was incremented/decremented on a field
+// with the given name. The second boolean return value indicates that this field
+// was not set, or was not defined in the schema.
+func (m *PaymentOrderMutation) AddedField(name string) (ent.Value, bool) {
+ switch name {
+ case paymentorder.FieldAmount:
+ return m.AddedAmount()
+ case paymentorder.FieldPayAmount:
+ return m.AddedPayAmount()
+ case paymentorder.FieldFeeRate:
+ return m.AddedFeeRate()
+ case paymentorder.FieldPlanID:
+ return m.AddedPlanID()
+ case paymentorder.FieldSubscriptionGroupID:
+ return m.AddedSubscriptionGroupID()
+ case paymentorder.FieldSubscriptionDays:
+ return m.AddedSubscriptionDays()
+ case paymentorder.FieldRefundAmount:
+ return m.AddedRefundAmount()
+ }
+ return nil, false
+}
+
+// AddField adds the value to the field with the given name. It returns an error if
+// the field is not defined in the schema, or if the type mismatched the field
+// type.
+func (m *PaymentOrderMutation) AddField(name string, value ent.Value) error {
+ switch name {
+ case paymentorder.FieldAmount:
+ v, ok := value.(float64)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.AddAmount(v)
+ return nil
+ case paymentorder.FieldPayAmount:
+ v, ok := value.(float64)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.AddPayAmount(v)
+ return nil
+ case paymentorder.FieldFeeRate:
+ v, ok := value.(float64)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.AddFeeRate(v)
+ return nil
+ case paymentorder.FieldPlanID:
+ v, ok := value.(int64)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.AddPlanID(v)
+ return nil
+ case paymentorder.FieldSubscriptionGroupID:
+ v, ok := value.(int64)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.AddSubscriptionGroupID(v)
+ return nil
+ case paymentorder.FieldSubscriptionDays:
+ v, ok := value.(int)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.AddSubscriptionDays(v)
+ return nil
+ case paymentorder.FieldRefundAmount:
+ v, ok := value.(float64)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.AddRefundAmount(v)
+ return nil
+ }
+ return fmt.Errorf("unknown PaymentOrder numeric field %s", name)
+}
+
+// ClearedFields returns all nullable fields that were cleared during this
+// mutation.
+func (m *PaymentOrderMutation) ClearedFields() []string {
+ var fields []string
+ if m.FieldCleared(paymentorder.FieldUserNotes) {
+ fields = append(fields, paymentorder.FieldUserNotes)
+ }
+ if m.FieldCleared(paymentorder.FieldPayURL) {
+ fields = append(fields, paymentorder.FieldPayURL)
+ }
+ if m.FieldCleared(paymentorder.FieldQrCode) {
+ fields = append(fields, paymentorder.FieldQrCode)
+ }
+ if m.FieldCleared(paymentorder.FieldQrCodeImg) {
+ fields = append(fields, paymentorder.FieldQrCodeImg)
+ }
+ if m.FieldCleared(paymentorder.FieldPlanID) {
+ fields = append(fields, paymentorder.FieldPlanID)
+ }
+ if m.FieldCleared(paymentorder.FieldSubscriptionGroupID) {
+ fields = append(fields, paymentorder.FieldSubscriptionGroupID)
+ }
+ if m.FieldCleared(paymentorder.FieldSubscriptionDays) {
+ fields = append(fields, paymentorder.FieldSubscriptionDays)
+ }
+ if m.FieldCleared(paymentorder.FieldProviderInstanceID) {
+ fields = append(fields, paymentorder.FieldProviderInstanceID)
+ }
+ if m.FieldCleared(paymentorder.FieldRefundReason) {
+ fields = append(fields, paymentorder.FieldRefundReason)
+ }
+ if m.FieldCleared(paymentorder.FieldRefundAt) {
+ fields = append(fields, paymentorder.FieldRefundAt)
+ }
+ if m.FieldCleared(paymentorder.FieldRefundRequestedAt) {
+ fields = append(fields, paymentorder.FieldRefundRequestedAt)
+ }
+ if m.FieldCleared(paymentorder.FieldRefundRequestReason) {
+ fields = append(fields, paymentorder.FieldRefundRequestReason)
+ }
+ if m.FieldCleared(paymentorder.FieldRefundRequestedBy) {
+ fields = append(fields, paymentorder.FieldRefundRequestedBy)
+ }
+ if m.FieldCleared(paymentorder.FieldPaidAt) {
+ fields = append(fields, paymentorder.FieldPaidAt)
+ }
+ if m.FieldCleared(paymentorder.FieldCompletedAt) {
+ fields = append(fields, paymentorder.FieldCompletedAt)
+ }
+ if m.FieldCleared(paymentorder.FieldFailedAt) {
+ fields = append(fields, paymentorder.FieldFailedAt)
+ }
+ if m.FieldCleared(paymentorder.FieldFailedReason) {
+ fields = append(fields, paymentorder.FieldFailedReason)
+ }
+ if m.FieldCleared(paymentorder.FieldSrcURL) {
+ fields = append(fields, paymentorder.FieldSrcURL)
+ }
+ return fields
+}
+
+// FieldCleared returns a boolean indicating if a field with the given name was
+// cleared in this mutation.
+func (m *PaymentOrderMutation) FieldCleared(name string) bool {
+ _, ok := m.clearedFields[name]
+ return ok
+}
+
+// ClearField clears the value of the field with the given name. It returns an
+// error if the field is not defined in the schema.
+func (m *PaymentOrderMutation) ClearField(name string) error {
+ switch name {
+ case paymentorder.FieldUserNotes:
+ m.ClearUserNotes()
+ return nil
+ case paymentorder.FieldPayURL:
+ m.ClearPayURL()
+ return nil
+ case paymentorder.FieldQrCode:
+ m.ClearQrCode()
+ return nil
+ case paymentorder.FieldQrCodeImg:
+ m.ClearQrCodeImg()
+ return nil
+ case paymentorder.FieldPlanID:
+ m.ClearPlanID()
+ return nil
+ case paymentorder.FieldSubscriptionGroupID:
+ m.ClearSubscriptionGroupID()
+ return nil
+ case paymentorder.FieldSubscriptionDays:
+ m.ClearSubscriptionDays()
+ return nil
+ case paymentorder.FieldProviderInstanceID:
+ m.ClearProviderInstanceID()
+ return nil
+ case paymentorder.FieldRefundReason:
+ m.ClearRefundReason()
+ return nil
+ case paymentorder.FieldRefundAt:
+ m.ClearRefundAt()
+ return nil
+ case paymentorder.FieldRefundRequestedAt:
+ m.ClearRefundRequestedAt()
+ return nil
+ case paymentorder.FieldRefundRequestReason:
+ m.ClearRefundRequestReason()
+ return nil
+ case paymentorder.FieldRefundRequestedBy:
+ m.ClearRefundRequestedBy()
+ return nil
+ case paymentorder.FieldPaidAt:
+ m.ClearPaidAt()
+ return nil
+ case paymentorder.FieldCompletedAt:
+ m.ClearCompletedAt()
+ return nil
+ case paymentorder.FieldFailedAt:
+ m.ClearFailedAt()
+ return nil
+ case paymentorder.FieldFailedReason:
+ m.ClearFailedReason()
+ return nil
+ case paymentorder.FieldSrcURL:
+ m.ClearSrcURL()
+ return nil
+ }
+ return fmt.Errorf("unknown PaymentOrder nullable field %s", name)
+}
+
+// ResetField resets all changes in the mutation for the field with the given name.
+// It returns an error if the field is not defined in the schema.
+func (m *PaymentOrderMutation) ResetField(name string) error {
+ switch name {
+ case paymentorder.FieldUserID:
+ m.ResetUserID()
+ return nil
+ case paymentorder.FieldUserEmail:
+ m.ResetUserEmail()
+ return nil
+ case paymentorder.FieldUserName:
+ m.ResetUserName()
+ return nil
+ case paymentorder.FieldUserNotes:
+ m.ResetUserNotes()
+ return nil
+ case paymentorder.FieldAmount:
+ m.ResetAmount()
+ return nil
+ case paymentorder.FieldPayAmount:
+ m.ResetPayAmount()
+ return nil
+ case paymentorder.FieldFeeRate:
+ m.ResetFeeRate()
+ return nil
+ case paymentorder.FieldRechargeCode:
+ m.ResetRechargeCode()
+ return nil
+ case paymentorder.FieldOutTradeNo:
+ m.ResetOutTradeNo()
+ return nil
+ case paymentorder.FieldPaymentType:
+ m.ResetPaymentType()
+ return nil
+ case paymentorder.FieldPaymentTradeNo:
+ m.ResetPaymentTradeNo()
+ return nil
+ case paymentorder.FieldPayURL:
+ m.ResetPayURL()
+ return nil
+ case paymentorder.FieldQrCode:
+ m.ResetQrCode()
+ return nil
+ case paymentorder.FieldQrCodeImg:
+ m.ResetQrCodeImg()
+ return nil
+ case paymentorder.FieldOrderType:
+ m.ResetOrderType()
+ return nil
+ case paymentorder.FieldPlanID:
+ m.ResetPlanID()
+ return nil
+ case paymentorder.FieldSubscriptionGroupID:
+ m.ResetSubscriptionGroupID()
+ return nil
+ case paymentorder.FieldSubscriptionDays:
+ m.ResetSubscriptionDays()
+ return nil
+ case paymentorder.FieldProviderInstanceID:
+ m.ResetProviderInstanceID()
+ return nil
+ case paymentorder.FieldStatus:
+ m.ResetStatus()
+ return nil
+ case paymentorder.FieldRefundAmount:
+ m.ResetRefundAmount()
+ return nil
+ case paymentorder.FieldRefundReason:
+ m.ResetRefundReason()
+ return nil
+ case paymentorder.FieldRefundAt:
+ m.ResetRefundAt()
+ return nil
+ case paymentorder.FieldForceRefund:
+ m.ResetForceRefund()
+ return nil
+ case paymentorder.FieldRefundRequestedAt:
+ m.ResetRefundRequestedAt()
+ return nil
+ case paymentorder.FieldRefundRequestReason:
+ m.ResetRefundRequestReason()
+ return nil
+ case paymentorder.FieldRefundRequestedBy:
+ m.ResetRefundRequestedBy()
+ return nil
+ case paymentorder.FieldExpiresAt:
+ m.ResetExpiresAt()
+ return nil
+ case paymentorder.FieldPaidAt:
+ m.ResetPaidAt()
+ return nil
+ case paymentorder.FieldCompletedAt:
+ m.ResetCompletedAt()
+ return nil
+ case paymentorder.FieldFailedAt:
+ m.ResetFailedAt()
+ return nil
+ case paymentorder.FieldFailedReason:
+ m.ResetFailedReason()
+ return nil
+ case paymentorder.FieldClientIP:
+ m.ResetClientIP()
+ return nil
+ case paymentorder.FieldSrcHost:
+ m.ResetSrcHost()
+ return nil
+ case paymentorder.FieldSrcURL:
+ m.ResetSrcURL()
+ return nil
+ case paymentorder.FieldCreatedAt:
+ m.ResetCreatedAt()
+ return nil
+ case paymentorder.FieldUpdatedAt:
+ m.ResetUpdatedAt()
+ return nil
+ }
+ return fmt.Errorf("unknown PaymentOrder field %s", name)
+}
+
+// AddedEdges returns all edge names that were set/added in this mutation.
+func (m *PaymentOrderMutation) AddedEdges() []string {
+ edges := make([]string, 0, 1)
+ if m.user != nil {
+ edges = append(edges, paymentorder.EdgeUser)
+ }
+ return edges
+}
+
+// AddedIDs returns all IDs (to other nodes) that were added for the given edge
+// name in this mutation.
+func (m *PaymentOrderMutation) AddedIDs(name string) []ent.Value {
+ switch name {
+ case paymentorder.EdgeUser:
+ if id := m.user; id != nil {
+ return []ent.Value{*id}
+ }
+ }
+ return nil
+}
+
+// RemovedEdges returns all edge names that were removed in this mutation.
+func (m *PaymentOrderMutation) RemovedEdges() []string {
+ edges := make([]string, 0, 1)
+ return edges
+}
+
+// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with
+// the given name in this mutation.
+func (m *PaymentOrderMutation) RemovedIDs(name string) []ent.Value {
+ return nil
+}
+
+// ClearedEdges returns all edge names that were cleared in this mutation.
+func (m *PaymentOrderMutation) ClearedEdges() []string {
+ edges := make([]string, 0, 1)
+ if m.cleareduser {
+ edges = append(edges, paymentorder.EdgeUser)
+ }
+ return edges
+}
+
+// EdgeCleared returns a boolean which indicates if the edge with the given name
+// was cleared in this mutation.
+func (m *PaymentOrderMutation) EdgeCleared(name string) bool {
+ switch name {
+ case paymentorder.EdgeUser:
+ return m.cleareduser
+ }
+ return false
+}
+
+// ClearEdge clears the value of the edge with the given name. It returns an error
+// if that edge is not defined in the schema.
+func (m *PaymentOrderMutation) ClearEdge(name string) error {
+ switch name {
+ case paymentorder.EdgeUser:
+ m.ClearUser()
+ return nil
+ }
+ return fmt.Errorf("unknown PaymentOrder unique edge %s", name)
+}
+
+// ResetEdge resets all changes to the edge with the given name in this mutation.
+// It returns an error if the edge is not defined in the schema.
+func (m *PaymentOrderMutation) ResetEdge(name string) error {
+ switch name {
+ case paymentorder.EdgeUser:
+ m.ResetUser()
+ return nil
+ }
+ return fmt.Errorf("unknown PaymentOrder edge %s", name)
+}
+
+// PaymentProviderInstanceMutation represents an operation that mutates the PaymentProviderInstance nodes in the graph.
+type PaymentProviderInstanceMutation struct {
+ config
+ op Op
+ typ string
+ id *int64
+ provider_key *string
+ name *string
+ _config *string
+ supported_types *string
+ enabled *bool
+ payment_mode *string
+ sort_order *int
+ addsort_order *int
+ limits *string
+ refund_enabled *bool
+ created_at *time.Time
+ updated_at *time.Time
+ clearedFields map[string]struct{}
+ done bool
+ oldValue func(context.Context) (*PaymentProviderInstance, error)
+ predicates []predicate.PaymentProviderInstance
+}
+
+var _ ent.Mutation = (*PaymentProviderInstanceMutation)(nil)
+
+// paymentproviderinstanceOption allows management of the mutation configuration using functional options.
+type paymentproviderinstanceOption func(*PaymentProviderInstanceMutation)
+
+// newPaymentProviderInstanceMutation creates new mutation for the PaymentProviderInstance entity.
+func newPaymentProviderInstanceMutation(c config, op Op, opts ...paymentproviderinstanceOption) *PaymentProviderInstanceMutation {
+ m := &PaymentProviderInstanceMutation{
+ config: c,
+ op: op,
+ typ: TypePaymentProviderInstance,
+ clearedFields: make(map[string]struct{}),
+ }
+ for _, opt := range opts {
+ opt(m)
+ }
+ return m
+}
+
+// withPaymentProviderInstanceID sets the ID field of the mutation.
+func withPaymentProviderInstanceID(id int64) paymentproviderinstanceOption {
+ return func(m *PaymentProviderInstanceMutation) {
+ var (
+ err error
+ once sync.Once
+ value *PaymentProviderInstance
+ )
+ m.oldValue = func(ctx context.Context) (*PaymentProviderInstance, error) {
+ once.Do(func() {
+ if m.done {
+ err = errors.New("querying old values post mutation is not allowed")
+ } else {
+ value, err = m.Client().PaymentProviderInstance.Get(ctx, id)
+ }
+ })
+ return value, err
+ }
+ m.id = &id
+ }
+}
+
+// withPaymentProviderInstance sets the old PaymentProviderInstance of the mutation.
+func withPaymentProviderInstance(node *PaymentProviderInstance) paymentproviderinstanceOption {
+ return func(m *PaymentProviderInstanceMutation) {
+ m.oldValue = func(context.Context) (*PaymentProviderInstance, error) {
+ return node, nil
+ }
+ m.id = &node.ID
+ }
+}
+
+// Client returns a new `ent.Client` from the mutation. If the mutation was
+// executed in a transaction (ent.Tx), a transactional client is returned.
+func (m PaymentProviderInstanceMutation) Client() *Client {
+ client := &Client{config: m.config}
+ client.init()
+ return client
+}
+
+// Tx returns an `ent.Tx` for mutations that were executed in transactions;
+// it returns an error otherwise.
+func (m PaymentProviderInstanceMutation) Tx() (*Tx, error) {
+ if _, ok := m.driver.(*txDriver); !ok {
+ return nil, errors.New("ent: mutation is not running in a transaction")
+ }
+ tx := &Tx{config: m.config}
+ tx.init()
+ return tx, nil
+}
+
+// ID returns the ID value in the mutation. Note that the ID is only available
+// if it was provided to the builder or after it was returned from the database.
+func (m *PaymentProviderInstanceMutation) ID() (id int64, exists bool) {
+ if m.id == nil {
+ return
+ }
+ return *m.id, true
+}
+
+// IDs queries the database and returns the entity ids that match the mutation's predicate.
+// That means, if the mutation is applied within a transaction with an isolation level such
+// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated
+// or updated by the mutation.
+func (m *PaymentProviderInstanceMutation) IDs(ctx context.Context) ([]int64, error) {
+ switch {
+ case m.op.Is(OpUpdateOne | OpDeleteOne):
+ id, exists := m.ID()
+ if exists {
+ return []int64{id}, nil
+ }
+ fallthrough
+ case m.op.Is(OpUpdate | OpDelete):
+ return m.Client().PaymentProviderInstance.Query().Where(m.predicates...).IDs(ctx)
+ default:
+ return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op)
+ }
+}
+
+// SetProviderKey sets the "provider_key" field.
+func (m *PaymentProviderInstanceMutation) SetProviderKey(s string) {
+ m.provider_key = &s
+}
+
+// ProviderKey returns the value of the "provider_key" field in the mutation.
+func (m *PaymentProviderInstanceMutation) ProviderKey() (r string, exists bool) {
+ v := m.provider_key
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldProviderKey returns the old "provider_key" field's value of the PaymentProviderInstance entity.
+// If the PaymentProviderInstance object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PaymentProviderInstanceMutation) OldProviderKey(ctx context.Context) (v string, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldProviderKey is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldProviderKey requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldProviderKey: %w", err)
+ }
+ return oldValue.ProviderKey, nil
+}
+
+// ResetProviderKey resets all changes to the "provider_key" field.
+func (m *PaymentProviderInstanceMutation) ResetProviderKey() {
+ m.provider_key = nil
+}
+
+// SetName sets the "name" field.
+func (m *PaymentProviderInstanceMutation) SetName(s string) {
+ m.name = &s
+}
+
+// Name returns the value of the "name" field in the mutation.
+func (m *PaymentProviderInstanceMutation) Name() (r string, exists bool) {
+ v := m.name
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldName returns the old "name" field's value of the PaymentProviderInstance entity.
+// If the PaymentProviderInstance object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PaymentProviderInstanceMutation) OldName(ctx context.Context) (v string, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldName is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldName requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldName: %w", err)
+ }
+ return oldValue.Name, nil
+}
+
+// ResetName resets all changes to the "name" field.
+func (m *PaymentProviderInstanceMutation) ResetName() {
+ m.name = nil
+}
+
+// SetConfig sets the "config" field.
+func (m *PaymentProviderInstanceMutation) SetConfig(s string) {
+ m._config = &s
+}
+
+// Config returns the value of the "config" field in the mutation.
+func (m *PaymentProviderInstanceMutation) Config() (r string, exists bool) {
+ v := m._config
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldConfig returns the old "config" field's value of the PaymentProviderInstance entity.
+// If the PaymentProviderInstance object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PaymentProviderInstanceMutation) OldConfig(ctx context.Context) (v string, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldConfig is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldConfig requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldConfig: %w", err)
+ }
+ return oldValue.Config, nil
+}
+
+// ResetConfig resets all changes to the "config" field.
+func (m *PaymentProviderInstanceMutation) ResetConfig() {
+ m._config = nil
+}
+
+// SetSupportedTypes sets the "supported_types" field.
+func (m *PaymentProviderInstanceMutation) SetSupportedTypes(s string) {
+ m.supported_types = &s
+}
+
+// SupportedTypes returns the value of the "supported_types" field in the mutation.
+func (m *PaymentProviderInstanceMutation) SupportedTypes() (r string, exists bool) {
+ v := m.supported_types
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldSupportedTypes returns the old "supported_types" field's value of the PaymentProviderInstance entity.
+// If the PaymentProviderInstance object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PaymentProviderInstanceMutation) OldSupportedTypes(ctx context.Context) (v string, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldSupportedTypes is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldSupportedTypes requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldSupportedTypes: %w", err)
+ }
+ return oldValue.SupportedTypes, nil
+}
+
+// ResetSupportedTypes resets all changes to the "supported_types" field.
+func (m *PaymentProviderInstanceMutation) ResetSupportedTypes() {
+ m.supported_types = nil
+}
+
+// SetEnabled sets the "enabled" field.
+func (m *PaymentProviderInstanceMutation) SetEnabled(b bool) {
+ m.enabled = &b
+}
+
+// Enabled returns the value of the "enabled" field in the mutation.
+func (m *PaymentProviderInstanceMutation) Enabled() (r bool, exists bool) {
+ v := m.enabled
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldEnabled returns the old "enabled" field's value of the PaymentProviderInstance entity.
+// If the PaymentProviderInstance object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PaymentProviderInstanceMutation) OldEnabled(ctx context.Context) (v bool, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldEnabled is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldEnabled requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldEnabled: %w", err)
+ }
+ return oldValue.Enabled, nil
+}
+
+// ResetEnabled resets all changes to the "enabled" field.
+func (m *PaymentProviderInstanceMutation) ResetEnabled() {
+ m.enabled = nil
+}
+
+// SetPaymentMode sets the "payment_mode" field.
+func (m *PaymentProviderInstanceMutation) SetPaymentMode(s string) {
+ m.payment_mode = &s
+}
+
+// PaymentMode returns the value of the "payment_mode" field in the mutation.
+func (m *PaymentProviderInstanceMutation) PaymentMode() (r string, exists bool) {
+ v := m.payment_mode
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldPaymentMode returns the old "payment_mode" field's value of the PaymentProviderInstance entity.
+// If the PaymentProviderInstance object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PaymentProviderInstanceMutation) OldPaymentMode(ctx context.Context) (v string, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldPaymentMode is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldPaymentMode requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldPaymentMode: %w", err)
+ }
+ return oldValue.PaymentMode, nil
+}
+
+// ResetPaymentMode resets all changes to the "payment_mode" field.
+func (m *PaymentProviderInstanceMutation) ResetPaymentMode() {
+ m.payment_mode = nil
+}
+
+// SetSortOrder sets the "sort_order" field.
+func (m *PaymentProviderInstanceMutation) SetSortOrder(i int) {
+ m.sort_order = &i
+ m.addsort_order = nil
+}
+
+// SortOrder returns the value of the "sort_order" field in the mutation.
+func (m *PaymentProviderInstanceMutation) SortOrder() (r int, exists bool) {
+ v := m.sort_order
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldSortOrder returns the old "sort_order" field's value of the PaymentProviderInstance entity.
+// If the PaymentProviderInstance object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PaymentProviderInstanceMutation) OldSortOrder(ctx context.Context) (v int, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldSortOrder is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldSortOrder requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldSortOrder: %w", err)
+ }
+ return oldValue.SortOrder, nil
+}
+
+// AddSortOrder adds i to the "sort_order" field.
+func (m *PaymentProviderInstanceMutation) AddSortOrder(i int) {
+ if m.addsort_order != nil {
+ *m.addsort_order += i
+ } else {
+ m.addsort_order = &i
+ }
+}
+
+// AddedSortOrder returns the value that was added to the "sort_order" field in this mutation.
+func (m *PaymentProviderInstanceMutation) AddedSortOrder() (r int, exists bool) {
+ v := m.addsort_order
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// ResetSortOrder resets all changes to the "sort_order" field.
+func (m *PaymentProviderInstanceMutation) ResetSortOrder() {
+ m.sort_order = nil
+ m.addsort_order = nil
+}
+
+// SetLimits sets the "limits" field.
+func (m *PaymentProviderInstanceMutation) SetLimits(s string) {
+ m.limits = &s
+}
+
+// Limits returns the value of the "limits" field in the mutation.
+func (m *PaymentProviderInstanceMutation) Limits() (r string, exists bool) {
+ v := m.limits
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldLimits returns the old "limits" field's value of the PaymentProviderInstance entity.
+// If the PaymentProviderInstance object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PaymentProviderInstanceMutation) OldLimits(ctx context.Context) (v string, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldLimits is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldLimits requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldLimits: %w", err)
+ }
+ return oldValue.Limits, nil
+}
+
+// ResetLimits resets all changes to the "limits" field.
+func (m *PaymentProviderInstanceMutation) ResetLimits() {
+ m.limits = nil
+}
+
+// SetRefundEnabled sets the "refund_enabled" field.
+func (m *PaymentProviderInstanceMutation) SetRefundEnabled(b bool) {
+ m.refund_enabled = &b
+}
+
+// RefundEnabled returns the value of the "refund_enabled" field in the mutation.
+func (m *PaymentProviderInstanceMutation) RefundEnabled() (r bool, exists bool) {
+ v := m.refund_enabled
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldRefundEnabled returns the old "refund_enabled" field's value of the PaymentProviderInstance entity.
+// If the PaymentProviderInstance object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PaymentProviderInstanceMutation) OldRefundEnabled(ctx context.Context) (v bool, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldRefundEnabled is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldRefundEnabled requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldRefundEnabled: %w", err)
+ }
+ return oldValue.RefundEnabled, nil
+}
+
+// ResetRefundEnabled resets all changes to the "refund_enabled" field.
+func (m *PaymentProviderInstanceMutation) ResetRefundEnabled() {
+ m.refund_enabled = nil
+}
+
+// SetCreatedAt sets the "created_at" field.
+func (m *PaymentProviderInstanceMutation) SetCreatedAt(t time.Time) {
+ m.created_at = &t
+}
+
+// CreatedAt returns the value of the "created_at" field in the mutation.
+func (m *PaymentProviderInstanceMutation) CreatedAt() (r time.Time, exists bool) {
+ v := m.created_at
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldCreatedAt returns the old "created_at" field's value of the PaymentProviderInstance entity.
+// If the PaymentProviderInstance object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PaymentProviderInstanceMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldCreatedAt requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err)
+ }
+ return oldValue.CreatedAt, nil
+}
+
+// ResetCreatedAt resets all changes to the "created_at" field.
+func (m *PaymentProviderInstanceMutation) ResetCreatedAt() {
+ m.created_at = nil
+}
+
+// SetUpdatedAt sets the "updated_at" field.
+func (m *PaymentProviderInstanceMutation) SetUpdatedAt(t time.Time) {
+ m.updated_at = &t
+}
+
+// UpdatedAt returns the value of the "updated_at" field in the mutation.
+func (m *PaymentProviderInstanceMutation) UpdatedAt() (r time.Time, exists bool) {
+ v := m.updated_at
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldUpdatedAt returns the old "updated_at" field's value of the PaymentProviderInstance entity.
+// If the PaymentProviderInstance object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PaymentProviderInstanceMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldUpdatedAt requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err)
+ }
+ return oldValue.UpdatedAt, nil
+}
+
+// ResetUpdatedAt resets all changes to the "updated_at" field.
+func (m *PaymentProviderInstanceMutation) ResetUpdatedAt() {
+ m.updated_at = nil
+}
+
+// Where appends a list predicates to the PaymentProviderInstanceMutation builder.
+func (m *PaymentProviderInstanceMutation) Where(ps ...predicate.PaymentProviderInstance) {
+ m.predicates = append(m.predicates, ps...)
+}
+
+// WhereP appends storage-level predicates to the PaymentProviderInstanceMutation builder. Using this method,
+// users can use type-assertion to append predicates that do not depend on any generated package.
+func (m *PaymentProviderInstanceMutation) WhereP(ps ...func(*sql.Selector)) {
+ p := make([]predicate.PaymentProviderInstance, len(ps))
+ for i := range ps {
+ p[i] = ps[i]
+ }
+ m.Where(p...)
+}
+
+// Op returns the operation name.
+func (m *PaymentProviderInstanceMutation) Op() Op {
+ return m.op
+}
+
+// SetOp allows setting the mutation operation.
+func (m *PaymentProviderInstanceMutation) SetOp(op Op) {
+ m.op = op
+}
+
+// Type returns the node type of this mutation (PaymentProviderInstance).
+func (m *PaymentProviderInstanceMutation) Type() string {
+ return m.typ
+}
+
+// Fields returns all fields that were changed during this mutation. Note that in
+// order to get all numeric fields that were incremented/decremented, call
+// AddedFields().
+func (m *PaymentProviderInstanceMutation) Fields() []string {
+ fields := make([]string, 0, 11)
+ if m.provider_key != nil {
+ fields = append(fields, paymentproviderinstance.FieldProviderKey)
+ }
+ if m.name != nil {
+ fields = append(fields, paymentproviderinstance.FieldName)
+ }
+ if m._config != nil {
+ fields = append(fields, paymentproviderinstance.FieldConfig)
+ }
+ if m.supported_types != nil {
+ fields = append(fields, paymentproviderinstance.FieldSupportedTypes)
+ }
+ if m.enabled != nil {
+ fields = append(fields, paymentproviderinstance.FieldEnabled)
+ }
+ if m.payment_mode != nil {
+ fields = append(fields, paymentproviderinstance.FieldPaymentMode)
+ }
+ if m.sort_order != nil {
+ fields = append(fields, paymentproviderinstance.FieldSortOrder)
+ }
+ if m.limits != nil {
+ fields = append(fields, paymentproviderinstance.FieldLimits)
+ }
+ if m.refund_enabled != nil {
+ fields = append(fields, paymentproviderinstance.FieldRefundEnabled)
+ }
+ if m.created_at != nil {
+ fields = append(fields, paymentproviderinstance.FieldCreatedAt)
+ }
+ if m.updated_at != nil {
+ fields = append(fields, paymentproviderinstance.FieldUpdatedAt)
+ }
+ return fields
+}
+
+// Field returns the value of a field with the given name. The second boolean
+// return value indicates that this field was not set, or was not defined in the
+// schema.
+func (m *PaymentProviderInstanceMutation) Field(name string) (ent.Value, bool) {
+ switch name {
+ case paymentproviderinstance.FieldProviderKey:
+ return m.ProviderKey()
+ case paymentproviderinstance.FieldName:
+ return m.Name()
+ case paymentproviderinstance.FieldConfig:
+ return m.Config()
+ case paymentproviderinstance.FieldSupportedTypes:
+ return m.SupportedTypes()
+ case paymentproviderinstance.FieldEnabled:
+ return m.Enabled()
+ case paymentproviderinstance.FieldPaymentMode:
+ return m.PaymentMode()
+ case paymentproviderinstance.FieldSortOrder:
+ return m.SortOrder()
+ case paymentproviderinstance.FieldLimits:
+ return m.Limits()
+ case paymentproviderinstance.FieldRefundEnabled:
+ return m.RefundEnabled()
+ case paymentproviderinstance.FieldCreatedAt:
+ return m.CreatedAt()
+ case paymentproviderinstance.FieldUpdatedAt:
+ return m.UpdatedAt()
+ }
+ return nil, false
+}
+
+// OldField returns the old value of the field from the database. An error is
+// returned if the mutation operation is not UpdateOne, or the query to the
+// database failed.
+func (m *PaymentProviderInstanceMutation) OldField(ctx context.Context, name string) (ent.Value, error) {
+ switch name {
+ case paymentproviderinstance.FieldProviderKey:
+ return m.OldProviderKey(ctx)
+ case paymentproviderinstance.FieldName:
+ return m.OldName(ctx)
+ case paymentproviderinstance.FieldConfig:
+ return m.OldConfig(ctx)
+ case paymentproviderinstance.FieldSupportedTypes:
+ return m.OldSupportedTypes(ctx)
+ case paymentproviderinstance.FieldEnabled:
+ return m.OldEnabled(ctx)
+ case paymentproviderinstance.FieldPaymentMode:
+ return m.OldPaymentMode(ctx)
+ case paymentproviderinstance.FieldSortOrder:
+ return m.OldSortOrder(ctx)
+ case paymentproviderinstance.FieldLimits:
+ return m.OldLimits(ctx)
+ case paymentproviderinstance.FieldRefundEnabled:
+ return m.OldRefundEnabled(ctx)
+ case paymentproviderinstance.FieldCreatedAt:
+ return m.OldCreatedAt(ctx)
+ case paymentproviderinstance.FieldUpdatedAt:
+ return m.OldUpdatedAt(ctx)
+ }
+ return nil, fmt.Errorf("unknown PaymentProviderInstance field %s", name)
+}
+
+// SetField sets the value of a field with the given name. It returns an error if
+// the field is not defined in the schema, or if the type mismatched the field
+// type.
+func (m *PaymentProviderInstanceMutation) SetField(name string, value ent.Value) error {
+ switch name {
+ case paymentproviderinstance.FieldProviderKey:
+ v, ok := value.(string)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetProviderKey(v)
+ return nil
+ case paymentproviderinstance.FieldName:
+ v, ok := value.(string)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetName(v)
+ return nil
+ case paymentproviderinstance.FieldConfig:
+ v, ok := value.(string)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetConfig(v)
+ return nil
+ case paymentproviderinstance.FieldSupportedTypes:
+ v, ok := value.(string)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetSupportedTypes(v)
+ return nil
+ case paymentproviderinstance.FieldEnabled:
+ v, ok := value.(bool)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetEnabled(v)
+ return nil
+ case paymentproviderinstance.FieldPaymentMode:
+ v, ok := value.(string)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetPaymentMode(v)
+ return nil
+ case paymentproviderinstance.FieldSortOrder:
+ v, ok := value.(int)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetSortOrder(v)
+ return nil
+ case paymentproviderinstance.FieldLimits:
+ v, ok := value.(string)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetLimits(v)
+ return nil
+ case paymentproviderinstance.FieldRefundEnabled:
+ v, ok := value.(bool)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetRefundEnabled(v)
+ return nil
+ case paymentproviderinstance.FieldCreatedAt:
+ v, ok := value.(time.Time)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetCreatedAt(v)
+ return nil
+ case paymentproviderinstance.FieldUpdatedAt:
+ v, ok := value.(time.Time)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetUpdatedAt(v)
+ return nil
+ }
+ return fmt.Errorf("unknown PaymentProviderInstance field %s", name)
+}
+
+// AddedFields returns all numeric fields that were incremented/decremented during
+// this mutation.
+func (m *PaymentProviderInstanceMutation) AddedFields() []string {
+ var fields []string
+ if m.addsort_order != nil {
+ fields = append(fields, paymentproviderinstance.FieldSortOrder)
+ }
+ return fields
+}
+
+// AddedField returns the numeric value that was incremented/decremented on a field
+// with the given name. The second boolean return value indicates that this field
+// was not set, or was not defined in the schema.
+func (m *PaymentProviderInstanceMutation) AddedField(name string) (ent.Value, bool) {
+ switch name {
+ case paymentproviderinstance.FieldSortOrder:
+ return m.AddedSortOrder()
+ }
+ return nil, false
+}
+
+// AddField adds the value to the field with the given name. It returns an error if
+// the field is not defined in the schema, or if the type mismatched the field
+// type.
+func (m *PaymentProviderInstanceMutation) AddField(name string, value ent.Value) error {
+ switch name {
+ case paymentproviderinstance.FieldSortOrder:
+ v, ok := value.(int)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.AddSortOrder(v)
+ return nil
+ }
+ return fmt.Errorf("unknown PaymentProviderInstance numeric field %s", name)
+}
+
+// ClearedFields returns all nullable fields that were cleared during this
+// mutation.
+func (m *PaymentProviderInstanceMutation) ClearedFields() []string {
+ return nil
+}
+
+// FieldCleared returns a boolean indicating if a field with the given name was
+// cleared in this mutation.
+func (m *PaymentProviderInstanceMutation) FieldCleared(name string) bool {
+ _, ok := m.clearedFields[name]
+ return ok
+}
+
+// ClearField clears the value of the field with the given name. It returns an
+// error if the field is not defined in the schema.
+func (m *PaymentProviderInstanceMutation) ClearField(name string) error {
+ return fmt.Errorf("unknown PaymentProviderInstance nullable field %s", name)
+}
+
+// ResetField resets all changes in the mutation for the field with the given name.
+// It returns an error if the field is not defined in the schema.
+func (m *PaymentProviderInstanceMutation) ResetField(name string) error {
+ switch name {
+ case paymentproviderinstance.FieldProviderKey:
+ m.ResetProviderKey()
+ return nil
+ case paymentproviderinstance.FieldName:
+ m.ResetName()
+ return nil
+ case paymentproviderinstance.FieldConfig:
+ m.ResetConfig()
+ return nil
+ case paymentproviderinstance.FieldSupportedTypes:
+ m.ResetSupportedTypes()
+ return nil
+ case paymentproviderinstance.FieldEnabled:
+ m.ResetEnabled()
+ return nil
+ case paymentproviderinstance.FieldPaymentMode:
+ m.ResetPaymentMode()
+ return nil
+ case paymentproviderinstance.FieldSortOrder:
+ m.ResetSortOrder()
+ return nil
+ case paymentproviderinstance.FieldLimits:
+ m.ResetLimits()
+ return nil
+ case paymentproviderinstance.FieldRefundEnabled:
+ m.ResetRefundEnabled()
+ return nil
+ case paymentproviderinstance.FieldCreatedAt:
+ m.ResetCreatedAt()
+ return nil
+ case paymentproviderinstance.FieldUpdatedAt:
+ m.ResetUpdatedAt()
+ return nil
+ }
+ return fmt.Errorf("unknown PaymentProviderInstance field %s", name)
+}
+
+// AddedEdges returns all edge names that were set/added in this mutation.
+func (m *PaymentProviderInstanceMutation) AddedEdges() []string {
+ edges := make([]string, 0, 0)
+ return edges
+}
+
+// AddedIDs returns all IDs (to other nodes) that were added for the given edge
+// name in this mutation.
+func (m *PaymentProviderInstanceMutation) AddedIDs(name string) []ent.Value {
+ return nil
+}
+
+// RemovedEdges returns all edge names that were removed in this mutation.
+func (m *PaymentProviderInstanceMutation) RemovedEdges() []string {
+ edges := make([]string, 0, 0)
+ return edges
+}
+
+// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with
+// the given name in this mutation.
+func (m *PaymentProviderInstanceMutation) RemovedIDs(name string) []ent.Value {
+ return nil
+}
+
+// ClearedEdges returns all edge names that were cleared in this mutation.
+func (m *PaymentProviderInstanceMutation) ClearedEdges() []string {
+ edges := make([]string, 0, 0)
+ return edges
+}
+
+// EdgeCleared returns a boolean which indicates if the edge with the given name
+// was cleared in this mutation.
+func (m *PaymentProviderInstanceMutation) EdgeCleared(name string) bool {
+ return false
+}
+
+// ClearEdge clears the value of the edge with the given name. It returns an error
+// if that edge is not defined in the schema.
+func (m *PaymentProviderInstanceMutation) ClearEdge(name string) error {
+ return fmt.Errorf("unknown PaymentProviderInstance unique edge %s", name)
+}
+
+// ResetEdge resets all changes to the edge with the given name in this mutation.
+// It returns an error if the edge is not defined in the schema.
+func (m *PaymentProviderInstanceMutation) ResetEdge(name string) error {
+ return fmt.Errorf("unknown PaymentProviderInstance edge %s", name)
+}
+
+// PromoCodeMutation represents an operation that mutates the PromoCode nodes in the graph.
+type PromoCodeMutation struct {
+ config
+ op Op
+ typ string
+ id *int64
+ code *string
+ bonus_amount *float64
+ addbonus_amount *float64
+ max_uses *int
+ addmax_uses *int
+ used_count *int
+ addused_count *int
+ status *string
+ expires_at *time.Time
+ notes *string
+ created_at *time.Time
+ updated_at *time.Time
+ clearedFields map[string]struct{}
+ usage_records map[int64]struct{}
+ removedusage_records map[int64]struct{}
+ clearedusage_records bool
+ done bool
+ oldValue func(context.Context) (*PromoCode, error)
+ predicates []predicate.PromoCode
+}
+
+var _ ent.Mutation = (*PromoCodeMutation)(nil)
+
+// promocodeOption allows management of the mutation configuration using functional options.
+type promocodeOption func(*PromoCodeMutation)
+
+// newPromoCodeMutation creates new mutation for the PromoCode entity.
+func newPromoCodeMutation(c config, op Op, opts ...promocodeOption) *PromoCodeMutation {
+ m := &PromoCodeMutation{
+ config: c,
+ op: op,
+ typ: TypePromoCode,
+ clearedFields: make(map[string]struct{}),
+ }
+ for _, opt := range opts {
+ opt(m)
+ }
+ return m
+}
+
+// withPromoCodeID sets the ID field of the mutation.
+func withPromoCodeID(id int64) promocodeOption {
+ return func(m *PromoCodeMutation) {
+ var (
+ err error
+ once sync.Once
+ value *PromoCode
+ )
+ m.oldValue = func(ctx context.Context) (*PromoCode, error) {
+ once.Do(func() {
+ if m.done {
+ err = errors.New("querying old values post mutation is not allowed")
+ } else {
+ value, err = m.Client().PromoCode.Get(ctx, id)
+ }
+ })
+ return value, err
+ }
+ m.id = &id
+ }
+}
+
+// withPromoCode sets the old PromoCode of the mutation.
+func withPromoCode(node *PromoCode) promocodeOption {
+ return func(m *PromoCodeMutation) {
+ m.oldValue = func(context.Context) (*PromoCode, error) {
+ return node, nil
+ }
+ m.id = &node.ID
+ }
+}
+
+// Client returns a new `ent.Client` from the mutation. If the mutation was
+// executed in a transaction (ent.Tx), a transactional client is returned.
+func (m PromoCodeMutation) Client() *Client {
+ client := &Client{config: m.config}
+ client.init()
+ return client
+}
+
+// Tx returns an `ent.Tx` for mutations that were executed in transactions;
+// it returns an error otherwise.
+func (m PromoCodeMutation) Tx() (*Tx, error) {
+ if _, ok := m.driver.(*txDriver); !ok {
+ return nil, errors.New("ent: mutation is not running in a transaction")
+ }
+ tx := &Tx{config: m.config}
+ tx.init()
+ return tx, nil
+}
+
+// ID returns the ID value in the mutation. Note that the ID is only available
+// if it was provided to the builder or after it was returned from the database.
+func (m *PromoCodeMutation) ID() (id int64, exists bool) {
+ if m.id == nil {
+ return
+ }
+ return *m.id, true
+}
+
+// IDs queries the database and returns the entity ids that match the mutation's predicate.
+// That means, if the mutation is applied within a transaction with an isolation level such
+// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated
+// or updated by the mutation.
+func (m *PromoCodeMutation) IDs(ctx context.Context) ([]int64, error) {
+ switch {
+ case m.op.Is(OpUpdateOne | OpDeleteOne):
+ id, exists := m.ID()
+ if exists {
+ return []int64{id}, nil
+ }
+ fallthrough
+ case m.op.Is(OpUpdate | OpDelete):
+ return m.Client().PromoCode.Query().Where(m.predicates...).IDs(ctx)
+ default:
+ return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op)
+ }
+}
+
+// SetCode sets the "code" field.
+func (m *PromoCodeMutation) SetCode(s string) {
+ m.code = &s
+}
+
+// Code returns the value of the "code" field in the mutation.
+func (m *PromoCodeMutation) Code() (r string, exists bool) {
+ v := m.code
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldCode returns the old "code" field's value of the PromoCode entity.
+// If the PromoCode object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PromoCodeMutation) OldCode(ctx context.Context) (v string, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldCode is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldCode requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldCode: %w", err)
+ }
+ return oldValue.Code, nil
+}
+
+// ResetCode resets all changes to the "code" field.
+func (m *PromoCodeMutation) ResetCode() {
+ m.code = nil
+}
+
+// SetBonusAmount sets the "bonus_amount" field.
+func (m *PromoCodeMutation) SetBonusAmount(f float64) {
+ m.bonus_amount = &f
+ m.addbonus_amount = nil
+}
+
+// BonusAmount returns the value of the "bonus_amount" field in the mutation.
+func (m *PromoCodeMutation) BonusAmount() (r float64, exists bool) {
+ v := m.bonus_amount
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldBonusAmount returns the old "bonus_amount" field's value of the PromoCode entity.
+// If the PromoCode object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PromoCodeMutation) OldBonusAmount(ctx context.Context) (v float64, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldBonusAmount is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldBonusAmount requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldBonusAmount: %w", err)
+ }
+ return oldValue.BonusAmount, nil
+}
+
+// AddBonusAmount adds f to the "bonus_amount" field.
+func (m *PromoCodeMutation) AddBonusAmount(f float64) {
+ if m.addbonus_amount != nil {
+ *m.addbonus_amount += f
+ } else {
+ m.addbonus_amount = &f
+ }
+}
+
+// AddedBonusAmount returns the value that was added to the "bonus_amount" field in this mutation.
+func (m *PromoCodeMutation) AddedBonusAmount() (r float64, exists bool) {
+ v := m.addbonus_amount
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// ResetBonusAmount resets all changes to the "bonus_amount" field.
+func (m *PromoCodeMutation) ResetBonusAmount() {
+ m.bonus_amount = nil
+ m.addbonus_amount = nil
+}
+
+// SetMaxUses sets the "max_uses" field.
+func (m *PromoCodeMutation) SetMaxUses(i int) {
+ m.max_uses = &i
+ m.addmax_uses = nil
+}
+
+// MaxUses returns the value of the "max_uses" field in the mutation.
+func (m *PromoCodeMutation) MaxUses() (r int, exists bool) {
+ v := m.max_uses
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldMaxUses returns the old "max_uses" field's value of the PromoCode entity.
+// If the PromoCode object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PromoCodeMutation) OldMaxUses(ctx context.Context) (v int, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldMaxUses is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldMaxUses requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldMaxUses: %w", err)
+ }
+ return oldValue.MaxUses, nil
+}
+
+// AddMaxUses adds i to the "max_uses" field.
+func (m *PromoCodeMutation) AddMaxUses(i int) {
+ if m.addmax_uses != nil {
+ *m.addmax_uses += i
+ } else {
+ m.addmax_uses = &i
+ }
+}
+
+// AddedMaxUses returns the value that was added to the "max_uses" field in this mutation.
+func (m *PromoCodeMutation) AddedMaxUses() (r int, exists bool) {
+ v := m.addmax_uses
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// ResetMaxUses resets all changes to the "max_uses" field.
+func (m *PromoCodeMutation) ResetMaxUses() {
+ m.max_uses = nil
+ m.addmax_uses = nil
+}
+
+// SetUsedCount sets the "used_count" field.
+func (m *PromoCodeMutation) SetUsedCount(i int) {
+ m.used_count = &i
+ m.addused_count = nil
+}
+
+// UsedCount returns the value of the "used_count" field in the mutation.
+func (m *PromoCodeMutation) UsedCount() (r int, exists bool) {
+ v := m.used_count
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldUsedCount returns the old "used_count" field's value of the PromoCode entity.
+// If the PromoCode object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PromoCodeMutation) OldUsedCount(ctx context.Context) (v int, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldUsedCount is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldUsedCount requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldUsedCount: %w", err)
+ }
+ return oldValue.UsedCount, nil
+}
+
+// AddUsedCount adds i to the "used_count" field.
+func (m *PromoCodeMutation) AddUsedCount(i int) {
+ if m.addused_count != nil {
+ *m.addused_count += i
+ } else {
+ m.addused_count = &i
+ }
+}
+
+// AddedUsedCount returns the value that was added to the "used_count" field in this mutation.
+func (m *PromoCodeMutation) AddedUsedCount() (r int, exists bool) {
+ v := m.addused_count
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// ResetUsedCount resets all changes to the "used_count" field.
+func (m *PromoCodeMutation) ResetUsedCount() {
+ m.used_count = nil
+ m.addused_count = nil
+}
+
+// SetStatus sets the "status" field.
+func (m *PromoCodeMutation) SetStatus(s string) {
+ m.status = &s
+}
+
+// Status returns the value of the "status" field in the mutation.
+func (m *PromoCodeMutation) Status() (r string, exists bool) {
+ v := m.status
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldStatus returns the old "status" field's value of the PromoCode entity.
+// If the PromoCode object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PromoCodeMutation) OldStatus(ctx context.Context) (v string, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldStatus is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldStatus requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldStatus: %w", err)
+ }
+ return oldValue.Status, nil
+}
+
+// ResetStatus resets all changes to the "status" field.
+func (m *PromoCodeMutation) ResetStatus() {
+ m.status = nil
+}
+
+// SetExpiresAt sets the "expires_at" field.
+func (m *PromoCodeMutation) SetExpiresAt(t time.Time) {
+ m.expires_at = &t
+}
+
+// ExpiresAt returns the value of the "expires_at" field in the mutation.
+func (m *PromoCodeMutation) ExpiresAt() (r time.Time, exists bool) {
+ v := m.expires_at
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldExpiresAt returns the old "expires_at" field's value of the PromoCode entity.
+// If the PromoCode object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PromoCodeMutation) OldExpiresAt(ctx context.Context) (v *time.Time, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldExpiresAt is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldExpiresAt requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldExpiresAt: %w", err)
+ }
+ return oldValue.ExpiresAt, nil
+}
+
+// ClearExpiresAt clears the value of the "expires_at" field.
+func (m *PromoCodeMutation) ClearExpiresAt() {
+ m.expires_at = nil
+ m.clearedFields[promocode.FieldExpiresAt] = struct{}{}
+}
+
+// ExpiresAtCleared returns if the "expires_at" field was cleared in this mutation.
+func (m *PromoCodeMutation) ExpiresAtCleared() bool {
+ _, ok := m.clearedFields[promocode.FieldExpiresAt]
+ return ok
+}
+
+// ResetExpiresAt resets all changes to the "expires_at" field.
+func (m *PromoCodeMutation) ResetExpiresAt() {
+ m.expires_at = nil
+ delete(m.clearedFields, promocode.FieldExpiresAt)
+}
+
+// SetNotes sets the "notes" field.
+func (m *PromoCodeMutation) SetNotes(s string) {
+ m.notes = &s
+}
+
+// Notes returns the value of the "notes" field in the mutation.
+func (m *PromoCodeMutation) Notes() (r string, exists bool) {
+ v := m.notes
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldNotes returns the old "notes" field's value of the PromoCode entity.
+// If the PromoCode object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PromoCodeMutation) OldNotes(ctx context.Context) (v *string, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldNotes is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldNotes requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldNotes: %w", err)
+ }
+ return oldValue.Notes, nil
+}
+
+// ClearNotes clears the value of the "notes" field.
+func (m *PromoCodeMutation) ClearNotes() {
+ m.notes = nil
+ m.clearedFields[promocode.FieldNotes] = struct{}{}
+}
+
+// NotesCleared returns if the "notes" field was cleared in this mutation.
+func (m *PromoCodeMutation) NotesCleared() bool {
+ _, ok := m.clearedFields[promocode.FieldNotes]
+ return ok
+}
+
+// ResetNotes resets all changes to the "notes" field.
+func (m *PromoCodeMutation) ResetNotes() {
+ m.notes = nil
+ delete(m.clearedFields, promocode.FieldNotes)
+}
+
+// SetCreatedAt sets the "created_at" field.
+func (m *PromoCodeMutation) SetCreatedAt(t time.Time) {
+ m.created_at = &t
+}
+
+// CreatedAt returns the value of the "created_at" field in the mutation.
+func (m *PromoCodeMutation) CreatedAt() (r time.Time, exists bool) {
+ v := m.created_at
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldCreatedAt returns the old "created_at" field's value of the PromoCode entity.
+// If the PromoCode object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PromoCodeMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldCreatedAt requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err)
+ }
+ return oldValue.CreatedAt, nil
+}
+
+// ResetCreatedAt resets all changes to the "created_at" field.
+func (m *PromoCodeMutation) ResetCreatedAt() {
+ m.created_at = nil
+}
+
+// SetUpdatedAt sets the "updated_at" field.
+func (m *PromoCodeMutation) SetUpdatedAt(t time.Time) {
+ m.updated_at = &t
+}
+
+// UpdatedAt returns the value of the "updated_at" field in the mutation.
+func (m *PromoCodeMutation) UpdatedAt() (r time.Time, exists bool) {
+ v := m.updated_at
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldUpdatedAt returns the old "updated_at" field's value of the PromoCode entity.
+// If the PromoCode object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PromoCodeMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldUpdatedAt requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err)
+ }
+ return oldValue.UpdatedAt, nil
+}
+
+// ResetUpdatedAt resets all changes to the "updated_at" field.
+func (m *PromoCodeMutation) ResetUpdatedAt() {
+ m.updated_at = nil
+}
+
+// AddUsageRecordIDs adds the "usage_records" edge to the PromoCodeUsage entity by ids.
+func (m *PromoCodeMutation) AddUsageRecordIDs(ids ...int64) {
+ if m.usage_records == nil {
+ m.usage_records = make(map[int64]struct{})
+ }
+ for i := range ids {
+ m.usage_records[ids[i]] = struct{}{}
+ }
+}
+
+// ClearUsageRecords clears the "usage_records" edge to the PromoCodeUsage entity.
+func (m *PromoCodeMutation) ClearUsageRecords() {
+ m.clearedusage_records = true
+}
+
+// UsageRecordsCleared reports if the "usage_records" edge to the PromoCodeUsage entity was cleared.
+func (m *PromoCodeMutation) UsageRecordsCleared() bool {
+ return m.clearedusage_records
+}
+
+// RemoveUsageRecordIDs removes the "usage_records" edge to the PromoCodeUsage entity by IDs.
+func (m *PromoCodeMutation) RemoveUsageRecordIDs(ids ...int64) {
+ if m.removedusage_records == nil {
+ m.removedusage_records = make(map[int64]struct{})
+ }
+ for i := range ids {
+ delete(m.usage_records, ids[i])
+ m.removedusage_records[ids[i]] = struct{}{}
+ }
+}
+
+// RemovedUsageRecords returns the removed IDs of the "usage_records" edge to the PromoCodeUsage entity.
+func (m *PromoCodeMutation) RemovedUsageRecordsIDs() (ids []int64) {
+ for id := range m.removedusage_records {
+ ids = append(ids, id)
+ }
+ return
+}
+
+// UsageRecordsIDs returns the "usage_records" edge IDs in the mutation.
+func (m *PromoCodeMutation) UsageRecordsIDs() (ids []int64) {
+ for id := range m.usage_records {
+ ids = append(ids, id)
+ }
+ return
+}
+
+// ResetUsageRecords resets all changes to the "usage_records" edge.
+func (m *PromoCodeMutation) ResetUsageRecords() {
+ m.usage_records = nil
+ m.clearedusage_records = false
+ m.removedusage_records = nil
+}
+
+// Where appends a list predicates to the PromoCodeMutation builder.
+func (m *PromoCodeMutation) Where(ps ...predicate.PromoCode) {
+ m.predicates = append(m.predicates, ps...)
+}
+
+// WhereP appends storage-level predicates to the PromoCodeMutation builder. Using this method,
+// users can use type-assertion to append predicates that do not depend on any generated package.
+func (m *PromoCodeMutation) WhereP(ps ...func(*sql.Selector)) {
+ p := make([]predicate.PromoCode, len(ps))
+ for i := range ps {
+ p[i] = ps[i]
+ }
+ m.Where(p...)
+}
+
+// Op returns the operation name.
+func (m *PromoCodeMutation) Op() Op {
+ return m.op
+}
+
+// SetOp allows setting the mutation operation.
+func (m *PromoCodeMutation) SetOp(op Op) {
+ m.op = op
+}
+
+// Type returns the node type of this mutation (PromoCode).
+func (m *PromoCodeMutation) Type() string {
+ return m.typ
+}
+
+// Fields returns all fields that were changed during this mutation. Note that in
+// order to get all numeric fields that were incremented/decremented, call
+// AddedFields().
+func (m *PromoCodeMutation) Fields() []string {
+ fields := make([]string, 0, 9)
+ if m.code != nil {
+ fields = append(fields, promocode.FieldCode)
+ }
+ if m.bonus_amount != nil {
+ fields = append(fields, promocode.FieldBonusAmount)
+ }
+ if m.max_uses != nil {
+ fields = append(fields, promocode.FieldMaxUses)
+ }
+ if m.used_count != nil {
+ fields = append(fields, promocode.FieldUsedCount)
+ }
+ if m.status != nil {
+ fields = append(fields, promocode.FieldStatus)
+ }
+ if m.expires_at != nil {
+ fields = append(fields, promocode.FieldExpiresAt)
+ }
+ if m.notes != nil {
+ fields = append(fields, promocode.FieldNotes)
+ }
+ if m.created_at != nil {
+ fields = append(fields, promocode.FieldCreatedAt)
+ }
+ if m.updated_at != nil {
+ fields = append(fields, promocode.FieldUpdatedAt)
+ }
+ return fields
+}
+
+// Field returns the value of a field with the given name. The second boolean
+// return value indicates that this field was not set, or was not defined in the
+// schema.
+func (m *PromoCodeMutation) Field(name string) (ent.Value, bool) {
+ switch name {
+ case promocode.FieldCode:
+ return m.Code()
+ case promocode.FieldBonusAmount:
+ return m.BonusAmount()
+ case promocode.FieldMaxUses:
+ return m.MaxUses()
+ case promocode.FieldUsedCount:
+ return m.UsedCount()
+ case promocode.FieldStatus:
+ return m.Status()
+ case promocode.FieldExpiresAt:
+ return m.ExpiresAt()
+ case promocode.FieldNotes:
+ return m.Notes()
+ case promocode.FieldCreatedAt:
+ return m.CreatedAt()
+ case promocode.FieldUpdatedAt:
+ return m.UpdatedAt()
+ }
+ return nil, false
+}
+
+// OldField returns the old value of the field from the database. An error is
+// returned if the mutation operation is not UpdateOne, or the query to the
+// database failed.
+func (m *PromoCodeMutation) OldField(ctx context.Context, name string) (ent.Value, error) {
+ switch name {
+ case promocode.FieldCode:
+ return m.OldCode(ctx)
+ case promocode.FieldBonusAmount:
+ return m.OldBonusAmount(ctx)
+ case promocode.FieldMaxUses:
+ return m.OldMaxUses(ctx)
+ case promocode.FieldUsedCount:
+ return m.OldUsedCount(ctx)
+ case promocode.FieldStatus:
+ return m.OldStatus(ctx)
+ case promocode.FieldExpiresAt:
+ return m.OldExpiresAt(ctx)
+ case promocode.FieldNotes:
+ return m.OldNotes(ctx)
+ case promocode.FieldCreatedAt:
+ return m.OldCreatedAt(ctx)
+ case promocode.FieldUpdatedAt:
+ return m.OldUpdatedAt(ctx)
+ }
+ return nil, fmt.Errorf("unknown PromoCode field %s", name)
+}
+
+// SetField sets the value of a field with the given name. It returns an error if
+// the field is not defined in the schema, or if the type mismatched the field
+// type.
+func (m *PromoCodeMutation) SetField(name string, value ent.Value) error {
+ switch name {
+ case promocode.FieldCode:
+ v, ok := value.(string)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetCode(v)
+ return nil
+ case promocode.FieldBonusAmount:
+ v, ok := value.(float64)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetBonusAmount(v)
+ return nil
+ case promocode.FieldMaxUses:
+ v, ok := value.(int)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetMaxUses(v)
+ return nil
+ case promocode.FieldUsedCount:
+ v, ok := value.(int)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetUsedCount(v)
+ return nil
+ case promocode.FieldStatus:
+ v, ok := value.(string)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetStatus(v)
+ return nil
+ case promocode.FieldExpiresAt:
+ v, ok := value.(time.Time)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetExpiresAt(v)
+ return nil
+ case promocode.FieldNotes:
+ v, ok := value.(string)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
m.SetNotes(v)
return nil
- case promocode.FieldCreatedAt:
- v, ok := value.(time.Time)
- if !ok {
- return fmt.Errorf("unexpected type %T for field %s", value, name)
- }
- m.SetCreatedAt(v)
+ case promocode.FieldCreatedAt:
+ v, ok := value.(time.Time)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetCreatedAt(v)
+ return nil
+ case promocode.FieldUpdatedAt:
+ v, ok := value.(time.Time)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetUpdatedAt(v)
+ return nil
+ }
+ return fmt.Errorf("unknown PromoCode field %s", name)
+}
+
+// AddedFields returns all numeric fields that were incremented/decremented during
+// this mutation.
+func (m *PromoCodeMutation) AddedFields() []string {
+ var fields []string
+ if m.addbonus_amount != nil {
+ fields = append(fields, promocode.FieldBonusAmount)
+ }
+ if m.addmax_uses != nil {
+ fields = append(fields, promocode.FieldMaxUses)
+ }
+ if m.addused_count != nil {
+ fields = append(fields, promocode.FieldUsedCount)
+ }
+ return fields
+}
+
+// AddedField returns the numeric value that was incremented/decremented on a field
+// with the given name. The second boolean return value indicates that this field
+// was not set, or was not defined in the schema.
+func (m *PromoCodeMutation) AddedField(name string) (ent.Value, bool) {
+ switch name {
+ case promocode.FieldBonusAmount:
+ return m.AddedBonusAmount()
+ case promocode.FieldMaxUses:
+ return m.AddedMaxUses()
+ case promocode.FieldUsedCount:
+ return m.AddedUsedCount()
+ }
+ return nil, false
+}
+
+// AddField adds the value to the field with the given name. It returns an error if
+// the field is not defined in the schema, or if the type mismatched the field
+// type.
+func (m *PromoCodeMutation) AddField(name string, value ent.Value) error {
+ switch name {
+ case promocode.FieldBonusAmount:
+ v, ok := value.(float64)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.AddBonusAmount(v)
+ return nil
+ case promocode.FieldMaxUses:
+ v, ok := value.(int)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.AddMaxUses(v)
+ return nil
+ case promocode.FieldUsedCount:
+ v, ok := value.(int)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.AddUsedCount(v)
+ return nil
+ }
+ return fmt.Errorf("unknown PromoCode numeric field %s", name)
+}
+
+// ClearedFields returns all nullable fields that were cleared during this
+// mutation.
+func (m *PromoCodeMutation) ClearedFields() []string {
+ var fields []string
+ if m.FieldCleared(promocode.FieldExpiresAt) {
+ fields = append(fields, promocode.FieldExpiresAt)
+ }
+ if m.FieldCleared(promocode.FieldNotes) {
+ fields = append(fields, promocode.FieldNotes)
+ }
+ return fields
+}
+
+// FieldCleared returns a boolean indicating if a field with the given name was
+// cleared in this mutation.
+func (m *PromoCodeMutation) FieldCleared(name string) bool {
+ _, ok := m.clearedFields[name]
+ return ok
+}
+
+// ClearField clears the value of the field with the given name. It returns an
+// error if the field is not defined in the schema.
+func (m *PromoCodeMutation) ClearField(name string) error {
+ switch name {
+ case promocode.FieldExpiresAt:
+ m.ClearExpiresAt()
+ return nil
+ case promocode.FieldNotes:
+ m.ClearNotes()
+ return nil
+ }
+ return fmt.Errorf("unknown PromoCode nullable field %s", name)
+}
+
+// ResetField resets all changes in the mutation for the field with the given name.
+// It returns an error if the field is not defined in the schema.
+func (m *PromoCodeMutation) ResetField(name string) error {
+ switch name {
+ case promocode.FieldCode:
+ m.ResetCode()
+ return nil
+ case promocode.FieldBonusAmount:
+ m.ResetBonusAmount()
+ return nil
+ case promocode.FieldMaxUses:
+ m.ResetMaxUses()
+ return nil
+ case promocode.FieldUsedCount:
+ m.ResetUsedCount()
+ return nil
+ case promocode.FieldStatus:
+ m.ResetStatus()
+ return nil
+ case promocode.FieldExpiresAt:
+ m.ResetExpiresAt()
+ return nil
+ case promocode.FieldNotes:
+ m.ResetNotes()
+ return nil
+ case promocode.FieldCreatedAt:
+ m.ResetCreatedAt()
+ return nil
+ case promocode.FieldUpdatedAt:
+ m.ResetUpdatedAt()
+ return nil
+ }
+ return fmt.Errorf("unknown PromoCode field %s", name)
+}
+
+// AddedEdges returns all edge names that were set/added in this mutation.
+func (m *PromoCodeMutation) AddedEdges() []string {
+ edges := make([]string, 0, 1)
+ if m.usage_records != nil {
+ edges = append(edges, promocode.EdgeUsageRecords)
+ }
+ return edges
+}
+
+// AddedIDs returns all IDs (to other nodes) that were added for the given edge
+// name in this mutation.
+func (m *PromoCodeMutation) AddedIDs(name string) []ent.Value {
+ switch name {
+ case promocode.EdgeUsageRecords:
+ ids := make([]ent.Value, 0, len(m.usage_records))
+ for id := range m.usage_records {
+ ids = append(ids, id)
+ }
+ return ids
+ }
+ return nil
+}
+
+// RemovedEdges returns all edge names that were removed in this mutation.
+func (m *PromoCodeMutation) RemovedEdges() []string {
+ edges := make([]string, 0, 1)
+ if m.removedusage_records != nil {
+ edges = append(edges, promocode.EdgeUsageRecords)
+ }
+ return edges
+}
+
+// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with
+// the given name in this mutation.
+func (m *PromoCodeMutation) RemovedIDs(name string) []ent.Value {
+ switch name {
+ case promocode.EdgeUsageRecords:
+ ids := make([]ent.Value, 0, len(m.removedusage_records))
+ for id := range m.removedusage_records {
+ ids = append(ids, id)
+ }
+ return ids
+ }
+ return nil
+}
+
+// ClearedEdges returns all edge names that were cleared in this mutation.
+func (m *PromoCodeMutation) ClearedEdges() []string {
+ edges := make([]string, 0, 1)
+ if m.clearedusage_records {
+ edges = append(edges, promocode.EdgeUsageRecords)
+ }
+ return edges
+}
+
+// EdgeCleared returns a boolean which indicates if the edge with the given name
+// was cleared in this mutation.
+func (m *PromoCodeMutation) EdgeCleared(name string) bool {
+ switch name {
+ case promocode.EdgeUsageRecords:
+ return m.clearedusage_records
+ }
+ return false
+}
+
+// ClearEdge clears the value of the edge with the given name. It returns an error
+// if that edge is not defined in the schema.
+func (m *PromoCodeMutation) ClearEdge(name string) error {
+ switch name {
+ }
+ return fmt.Errorf("unknown PromoCode unique edge %s", name)
+}
+
+// ResetEdge resets all changes to the edge with the given name in this mutation.
+// It returns an error if the edge is not defined in the schema.
+func (m *PromoCodeMutation) ResetEdge(name string) error {
+ switch name {
+ case promocode.EdgeUsageRecords:
+ m.ResetUsageRecords()
+ return nil
+ }
+ return fmt.Errorf("unknown PromoCode edge %s", name)
+}
+
+// PromoCodeUsageMutation represents an operation that mutates the PromoCodeUsage nodes in the graph.
+type PromoCodeUsageMutation struct {
+ config
+ op Op
+ typ string
+ id *int64
+ bonus_amount *float64
+ addbonus_amount *float64
+ used_at *time.Time
+ clearedFields map[string]struct{}
+ promo_code *int64
+ clearedpromo_code bool
+ user *int64
+ cleareduser bool
+ done bool
+ oldValue func(context.Context) (*PromoCodeUsage, error)
+ predicates []predicate.PromoCodeUsage
+}
+
+var _ ent.Mutation = (*PromoCodeUsageMutation)(nil)
+
+// promocodeusageOption allows management of the mutation configuration using functional options.
+type promocodeusageOption func(*PromoCodeUsageMutation)
+
+// newPromoCodeUsageMutation creates new mutation for the PromoCodeUsage entity.
+func newPromoCodeUsageMutation(c config, op Op, opts ...promocodeusageOption) *PromoCodeUsageMutation {
+ m := &PromoCodeUsageMutation{
+ config: c,
+ op: op,
+ typ: TypePromoCodeUsage,
+ clearedFields: make(map[string]struct{}),
+ }
+ for _, opt := range opts {
+ opt(m)
+ }
+ return m
+}
+
+// withPromoCodeUsageID sets the ID field of the mutation.
+func withPromoCodeUsageID(id int64) promocodeusageOption {
+ return func(m *PromoCodeUsageMutation) {
+ var (
+ err error
+ once sync.Once
+ value *PromoCodeUsage
+ )
+ m.oldValue = func(ctx context.Context) (*PromoCodeUsage, error) {
+ once.Do(func() {
+ if m.done {
+ err = errors.New("querying old values post mutation is not allowed")
+ } else {
+ value, err = m.Client().PromoCodeUsage.Get(ctx, id)
+ }
+ })
+ return value, err
+ }
+ m.id = &id
+ }
+}
+
+// withPromoCodeUsage sets the old PromoCodeUsage of the mutation.
+func withPromoCodeUsage(node *PromoCodeUsage) promocodeusageOption {
+ return func(m *PromoCodeUsageMutation) {
+ m.oldValue = func(context.Context) (*PromoCodeUsage, error) {
+ return node, nil
+ }
+ m.id = &node.ID
+ }
+}
+
+// Client returns a new `ent.Client` from the mutation. If the mutation was
+// executed in a transaction (ent.Tx), a transactional client is returned.
+func (m PromoCodeUsageMutation) Client() *Client {
+ client := &Client{config: m.config}
+ client.init()
+ return client
+}
+
+// Tx returns an `ent.Tx` for mutations that were executed in transactions;
+// it returns an error otherwise.
+func (m PromoCodeUsageMutation) Tx() (*Tx, error) {
+ if _, ok := m.driver.(*txDriver); !ok {
+ return nil, errors.New("ent: mutation is not running in a transaction")
+ }
+ tx := &Tx{config: m.config}
+ tx.init()
+ return tx, nil
+}
+
+// ID returns the ID value in the mutation. Note that the ID is only available
+// if it was provided to the builder or after it was returned from the database.
+func (m *PromoCodeUsageMutation) ID() (id int64, exists bool) {
+ if m.id == nil {
+ return
+ }
+ return *m.id, true
+}
+
+// IDs queries the database and returns the entity ids that match the mutation's predicate.
+// That means, if the mutation is applied within a transaction with an isolation level such
+// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated
+// or updated by the mutation.
+func (m *PromoCodeUsageMutation) IDs(ctx context.Context) ([]int64, error) {
+ switch {
+ case m.op.Is(OpUpdateOne | OpDeleteOne):
+ id, exists := m.ID()
+ if exists {
+ return []int64{id}, nil
+ }
+ fallthrough
+ case m.op.Is(OpUpdate | OpDelete):
+ return m.Client().PromoCodeUsage.Query().Where(m.predicates...).IDs(ctx)
+ default:
+ return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op)
+ }
+}
+
+// SetPromoCodeID sets the "promo_code_id" field.
+func (m *PromoCodeUsageMutation) SetPromoCodeID(i int64) {
+ m.promo_code = &i
+}
+
+// PromoCodeID returns the value of the "promo_code_id" field in the mutation.
+func (m *PromoCodeUsageMutation) PromoCodeID() (r int64, exists bool) {
+ v := m.promo_code
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldPromoCodeID returns the old "promo_code_id" field's value of the PromoCodeUsage entity.
+// If the PromoCodeUsage object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PromoCodeUsageMutation) OldPromoCodeID(ctx context.Context) (v int64, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldPromoCodeID is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldPromoCodeID requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldPromoCodeID: %w", err)
+ }
+ return oldValue.PromoCodeID, nil
+}
+
+// ResetPromoCodeID resets all changes to the "promo_code_id" field.
+func (m *PromoCodeUsageMutation) ResetPromoCodeID() {
+ m.promo_code = nil
+}
+
+// SetUserID sets the "user_id" field.
+func (m *PromoCodeUsageMutation) SetUserID(i int64) {
+ m.user = &i
+}
+
+// UserID returns the value of the "user_id" field in the mutation.
+func (m *PromoCodeUsageMutation) UserID() (r int64, exists bool) {
+ v := m.user
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldUserID returns the old "user_id" field's value of the PromoCodeUsage entity.
+// If the PromoCodeUsage object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PromoCodeUsageMutation) OldUserID(ctx context.Context) (v int64, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldUserID is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldUserID requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldUserID: %w", err)
+ }
+ return oldValue.UserID, nil
+}
+
+// ResetUserID resets all changes to the "user_id" field.
+func (m *PromoCodeUsageMutation) ResetUserID() {
+ m.user = nil
+}
+
+// SetBonusAmount sets the "bonus_amount" field.
+func (m *PromoCodeUsageMutation) SetBonusAmount(f float64) {
+ m.bonus_amount = &f
+ m.addbonus_amount = nil
+}
+
+// BonusAmount returns the value of the "bonus_amount" field in the mutation.
+func (m *PromoCodeUsageMutation) BonusAmount() (r float64, exists bool) {
+ v := m.bonus_amount
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldBonusAmount returns the old "bonus_amount" field's value of the PromoCodeUsage entity.
+// If the PromoCodeUsage object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PromoCodeUsageMutation) OldBonusAmount(ctx context.Context) (v float64, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldBonusAmount is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldBonusAmount requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldBonusAmount: %w", err)
+ }
+ return oldValue.BonusAmount, nil
+}
+
+// AddBonusAmount adds f to the "bonus_amount" field.
+func (m *PromoCodeUsageMutation) AddBonusAmount(f float64) {
+ if m.addbonus_amount != nil {
+ *m.addbonus_amount += f
+ } else {
+ m.addbonus_amount = &f
+ }
+}
+
+// AddedBonusAmount returns the value that was added to the "bonus_amount" field in this mutation.
+func (m *PromoCodeUsageMutation) AddedBonusAmount() (r float64, exists bool) {
+ v := m.addbonus_amount
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// ResetBonusAmount resets all changes to the "bonus_amount" field.
+func (m *PromoCodeUsageMutation) ResetBonusAmount() {
+ m.bonus_amount = nil
+ m.addbonus_amount = nil
+}
+
+// SetUsedAt sets the "used_at" field.
+func (m *PromoCodeUsageMutation) SetUsedAt(t time.Time) {
+ m.used_at = &t
+}
+
+// UsedAt returns the value of the "used_at" field in the mutation.
+func (m *PromoCodeUsageMutation) UsedAt() (r time.Time, exists bool) {
+ v := m.used_at
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldUsedAt returns the old "used_at" field's value of the PromoCodeUsage entity.
+// If the PromoCodeUsage object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PromoCodeUsageMutation) OldUsedAt(ctx context.Context) (v time.Time, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldUsedAt is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldUsedAt requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldUsedAt: %w", err)
+ }
+ return oldValue.UsedAt, nil
+}
+
+// ResetUsedAt resets all changes to the "used_at" field.
+func (m *PromoCodeUsageMutation) ResetUsedAt() {
+ m.used_at = nil
+}
+
+// ClearPromoCode clears the "promo_code" edge to the PromoCode entity.
+func (m *PromoCodeUsageMutation) ClearPromoCode() {
+ m.clearedpromo_code = true
+ m.clearedFields[promocodeusage.FieldPromoCodeID] = struct{}{}
+}
+
+// PromoCodeCleared reports if the "promo_code" edge to the PromoCode entity was cleared.
+func (m *PromoCodeUsageMutation) PromoCodeCleared() bool {
+ return m.clearedpromo_code
+}
+
+// PromoCodeIDs returns the "promo_code" edge IDs in the mutation.
+// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use
+// PromoCodeID instead. It exists only for internal usage by the builders.
+func (m *PromoCodeUsageMutation) PromoCodeIDs() (ids []int64) {
+ if id := m.promo_code; id != nil {
+ ids = append(ids, *id)
+ }
+ return
+}
+
+// ResetPromoCode resets all changes to the "promo_code" edge.
+func (m *PromoCodeUsageMutation) ResetPromoCode() {
+ m.promo_code = nil
+ m.clearedpromo_code = false
+}
+
+// ClearUser clears the "user" edge to the User entity.
+func (m *PromoCodeUsageMutation) ClearUser() {
+ m.cleareduser = true
+ m.clearedFields[promocodeusage.FieldUserID] = struct{}{}
+}
+
+// UserCleared reports if the "user" edge to the User entity was cleared.
+func (m *PromoCodeUsageMutation) UserCleared() bool {
+ return m.cleareduser
+}
+
+// UserIDs returns the "user" edge IDs in the mutation.
+// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use
+// UserID instead. It exists only for internal usage by the builders.
+func (m *PromoCodeUsageMutation) UserIDs() (ids []int64) {
+ if id := m.user; id != nil {
+ ids = append(ids, *id)
+ }
+ return
+}
+
+// ResetUser resets all changes to the "user" edge.
+func (m *PromoCodeUsageMutation) ResetUser() {
+ m.user = nil
+ m.cleareduser = false
+}
+
+// Where appends a list predicates to the PromoCodeUsageMutation builder.
+func (m *PromoCodeUsageMutation) Where(ps ...predicate.PromoCodeUsage) {
+ m.predicates = append(m.predicates, ps...)
+}
+
+// WhereP appends storage-level predicates to the PromoCodeUsageMutation builder. Using this method,
+// users can use type-assertion to append predicates that do not depend on any generated package.
+func (m *PromoCodeUsageMutation) WhereP(ps ...func(*sql.Selector)) {
+ p := make([]predicate.PromoCodeUsage, len(ps))
+ for i := range ps {
+ p[i] = ps[i]
+ }
+ m.Where(p...)
+}
+
+// Op returns the operation name.
+func (m *PromoCodeUsageMutation) Op() Op {
+ return m.op
+}
+
+// SetOp allows setting the mutation operation.
+func (m *PromoCodeUsageMutation) SetOp(op Op) {
+ m.op = op
+}
+
+// Type returns the node type of this mutation (PromoCodeUsage).
+func (m *PromoCodeUsageMutation) Type() string {
+ return m.typ
+}
+
+// Fields returns all fields that were changed during this mutation. Note that in
+// order to get all numeric fields that were incremented/decremented, call
+// AddedFields().
+func (m *PromoCodeUsageMutation) Fields() []string {
+ fields := make([]string, 0, 4)
+ if m.promo_code != nil {
+ fields = append(fields, promocodeusage.FieldPromoCodeID)
+ }
+ if m.user != nil {
+ fields = append(fields, promocodeusage.FieldUserID)
+ }
+ if m.bonus_amount != nil {
+ fields = append(fields, promocodeusage.FieldBonusAmount)
+ }
+ if m.used_at != nil {
+ fields = append(fields, promocodeusage.FieldUsedAt)
+ }
+ return fields
+}
+
+// Field returns the value of a field with the given name. The second boolean
+// return value indicates that this field was not set, or was not defined in the
+// schema.
+func (m *PromoCodeUsageMutation) Field(name string) (ent.Value, bool) {
+ switch name {
+ case promocodeusage.FieldPromoCodeID:
+ return m.PromoCodeID()
+ case promocodeusage.FieldUserID:
+ return m.UserID()
+ case promocodeusage.FieldBonusAmount:
+ return m.BonusAmount()
+ case promocodeusage.FieldUsedAt:
+ return m.UsedAt()
+ }
+ return nil, false
+}
+
+// OldField returns the old value of the field from the database. An error is
+// returned if the mutation operation is not UpdateOne, or the query to the
+// database failed.
+func (m *PromoCodeUsageMutation) OldField(ctx context.Context, name string) (ent.Value, error) {
+ switch name {
+ case promocodeusage.FieldPromoCodeID:
+ return m.OldPromoCodeID(ctx)
+ case promocodeusage.FieldUserID:
+ return m.OldUserID(ctx)
+ case promocodeusage.FieldBonusAmount:
+ return m.OldBonusAmount(ctx)
+ case promocodeusage.FieldUsedAt:
+ return m.OldUsedAt(ctx)
+ }
+ return nil, fmt.Errorf("unknown PromoCodeUsage field %s", name)
+}
+
+// SetField sets the value of a field with the given name. It returns an error if
+// the field is not defined in the schema, or if the type mismatched the field
+// type.
+func (m *PromoCodeUsageMutation) SetField(name string, value ent.Value) error {
+ switch name {
+ case promocodeusage.FieldPromoCodeID:
+ v, ok := value.(int64)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetPromoCodeID(v)
+ return nil
+ case promocodeusage.FieldUserID:
+ v, ok := value.(int64)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetUserID(v)
+ return nil
+ case promocodeusage.FieldBonusAmount:
+ v, ok := value.(float64)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetBonusAmount(v)
+ return nil
+ case promocodeusage.FieldUsedAt:
+ v, ok := value.(time.Time)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetUsedAt(v)
+ return nil
+ }
+ return fmt.Errorf("unknown PromoCodeUsage field %s", name)
+}
+
+// AddedFields returns all numeric fields that were incremented/decremented during
+// this mutation.
+func (m *PromoCodeUsageMutation) AddedFields() []string {
+ var fields []string
+ if m.addbonus_amount != nil {
+ fields = append(fields, promocodeusage.FieldBonusAmount)
+ }
+ return fields
+}
+
+// AddedField returns the numeric value that was incremented/decremented on a field
+// with the given name. The second boolean return value indicates that this field
+// was not set, or was not defined in the schema.
+func (m *PromoCodeUsageMutation) AddedField(name string) (ent.Value, bool) {
+ switch name {
+ case promocodeusage.FieldBonusAmount:
+ return m.AddedBonusAmount()
+ }
+ return nil, false
+}
+
+// AddField adds the value to the field with the given name. It returns an error if
+// the field is not defined in the schema, or if the type mismatched the field
+// type.
+func (m *PromoCodeUsageMutation) AddField(name string, value ent.Value) error {
+ switch name {
+ case promocodeusage.FieldBonusAmount:
+ v, ok := value.(float64)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.AddBonusAmount(v)
+ return nil
+ }
+ return fmt.Errorf("unknown PromoCodeUsage numeric field %s", name)
+}
+
+// ClearedFields returns all nullable fields that were cleared during this
+// mutation.
+func (m *PromoCodeUsageMutation) ClearedFields() []string {
+ return nil
+}
+
+// FieldCleared returns a boolean indicating if a field with the given name was
+// cleared in this mutation.
+func (m *PromoCodeUsageMutation) FieldCleared(name string) bool {
+ _, ok := m.clearedFields[name]
+ return ok
+}
+
+// ClearField clears the value of the field with the given name. It returns an
+// error if the field is not defined in the schema.
+func (m *PromoCodeUsageMutation) ClearField(name string) error {
+ return fmt.Errorf("unknown PromoCodeUsage nullable field %s", name)
+}
+
+// ResetField resets all changes in the mutation for the field with the given name.
+// It returns an error if the field is not defined in the schema.
+func (m *PromoCodeUsageMutation) ResetField(name string) error {
+ switch name {
+ case promocodeusage.FieldPromoCodeID:
+ m.ResetPromoCodeID()
+ return nil
+ case promocodeusage.FieldUserID:
+ m.ResetUserID()
return nil
- case promocode.FieldUpdatedAt:
- v, ok := value.(time.Time)
- if !ok {
- return fmt.Errorf("unexpected type %T for field %s", value, name)
+ case promocodeusage.FieldBonusAmount:
+ m.ResetBonusAmount()
+ return nil
+ case promocodeusage.FieldUsedAt:
+ m.ResetUsedAt()
+ return nil
+ }
+ return fmt.Errorf("unknown PromoCodeUsage field %s", name)
+}
+
+// AddedEdges returns all edge names that were set/added in this mutation.
+func (m *PromoCodeUsageMutation) AddedEdges() []string {
+ edges := make([]string, 0, 2)
+ if m.promo_code != nil {
+ edges = append(edges, promocodeusage.EdgePromoCode)
+ }
+ if m.user != nil {
+ edges = append(edges, promocodeusage.EdgeUser)
+ }
+ return edges
+}
+
+// AddedIDs returns all IDs (to other nodes) that were added for the given edge
+// name in this mutation.
+func (m *PromoCodeUsageMutation) AddedIDs(name string) []ent.Value {
+ switch name {
+ case promocodeusage.EdgePromoCode:
+ if id := m.promo_code; id != nil {
+ return []ent.Value{*id}
}
- m.SetUpdatedAt(v)
+ case promocodeusage.EdgeUser:
+ if id := m.user; id != nil {
+ return []ent.Value{*id}
+ }
+ }
+ return nil
+}
+
+// RemovedEdges returns all edge names that were removed in this mutation.
+func (m *PromoCodeUsageMutation) RemovedEdges() []string {
+ edges := make([]string, 0, 2)
+ return edges
+}
+
+// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with
+// the given name in this mutation.
+func (m *PromoCodeUsageMutation) RemovedIDs(name string) []ent.Value {
+ return nil
+}
+
+// ClearedEdges returns all edge names that were cleared in this mutation.
+func (m *PromoCodeUsageMutation) ClearedEdges() []string {
+ edges := make([]string, 0, 2)
+ if m.clearedpromo_code {
+ edges = append(edges, promocodeusage.EdgePromoCode)
+ }
+ if m.cleareduser {
+ edges = append(edges, promocodeusage.EdgeUser)
+ }
+ return edges
+}
+
+// EdgeCleared returns a boolean which indicates if the edge with the given name
+// was cleared in this mutation.
+func (m *PromoCodeUsageMutation) EdgeCleared(name string) bool {
+ switch name {
+ case promocodeusage.EdgePromoCode:
+ return m.clearedpromo_code
+ case promocodeusage.EdgeUser:
+ return m.cleareduser
+ }
+ return false
+}
+
+// ClearEdge clears the value of the edge with the given name. It returns an error
+// if that edge is not defined in the schema.
+func (m *PromoCodeUsageMutation) ClearEdge(name string) error {
+ switch name {
+ case promocodeusage.EdgePromoCode:
+ m.ClearPromoCode()
+ return nil
+ case promocodeusage.EdgeUser:
+ m.ClearUser()
+ return nil
+ }
+ return fmt.Errorf("unknown PromoCodeUsage unique edge %s", name)
+}
+
+// ResetEdge resets all changes to the edge with the given name in this mutation.
+// It returns an error if the edge is not defined in the schema.
+func (m *PromoCodeUsageMutation) ResetEdge(name string) error {
+ switch name {
+ case promocodeusage.EdgePromoCode:
+ m.ResetPromoCode()
+ return nil
+ case promocodeusage.EdgeUser:
+ m.ResetUser()
return nil
}
- return fmt.Errorf("unknown PromoCode field %s", name)
+ return fmt.Errorf("unknown PromoCodeUsage edge %s", name)
+}
+
+// ProxyMutation represents an operation that mutates the Proxy nodes in the graph.
+type ProxyMutation struct {
+ config
+ op Op
+ typ string
+ id *int64
+ created_at *time.Time
+ updated_at *time.Time
+ deleted_at *time.Time
+ name *string
+ protocol *string
+ host *string
+ port *int
+ addport *int
+ username *string
+ password *string
+ status *string
+ clearedFields map[string]struct{}
+ accounts map[int64]struct{}
+ removedaccounts map[int64]struct{}
+ clearedaccounts bool
+ done bool
+ oldValue func(context.Context) (*Proxy, error)
+ predicates []predicate.Proxy
+}
+
+var _ ent.Mutation = (*ProxyMutation)(nil)
+
+// proxyOption allows management of the mutation configuration using functional options.
+type proxyOption func(*ProxyMutation)
+
+// newProxyMutation creates new mutation for the Proxy entity.
+func newProxyMutation(c config, op Op, opts ...proxyOption) *ProxyMutation {
+ m := &ProxyMutation{
+ config: c,
+ op: op,
+ typ: TypeProxy,
+ clearedFields: make(map[string]struct{}),
+ }
+ for _, opt := range opts {
+ opt(m)
+ }
+ return m
+}
+
+// withProxyID sets the ID field of the mutation.
+func withProxyID(id int64) proxyOption {
+ return func(m *ProxyMutation) {
+ var (
+ err error
+ once sync.Once
+ value *Proxy
+ )
+ m.oldValue = func(ctx context.Context) (*Proxy, error) {
+ once.Do(func() {
+ if m.done {
+ err = errors.New("querying old values post mutation is not allowed")
+ } else {
+ value, err = m.Client().Proxy.Get(ctx, id)
+ }
+ })
+ return value, err
+ }
+ m.id = &id
+ }
+}
+
+// withProxy sets the old Proxy of the mutation.
+func withProxy(node *Proxy) proxyOption {
+ return func(m *ProxyMutation) {
+ m.oldValue = func(context.Context) (*Proxy, error) {
+ return node, nil
+ }
+ m.id = &node.ID
+ }
+}
+
+// Client returns a new `ent.Client` from the mutation. If the mutation was
+// executed in a transaction (ent.Tx), a transactional client is returned.
+func (m ProxyMutation) Client() *Client {
+ client := &Client{config: m.config}
+ client.init()
+ return client
+}
+
+// Tx returns an `ent.Tx` for mutations that were executed in transactions;
+// it returns an error otherwise.
+func (m ProxyMutation) Tx() (*Tx, error) {
+ if _, ok := m.driver.(*txDriver); !ok {
+ return nil, errors.New("ent: mutation is not running in a transaction")
+ }
+ tx := &Tx{config: m.config}
+ tx.init()
+ return tx, nil
+}
+
+// ID returns the ID value in the mutation. Note that the ID is only available
+// if it was provided to the builder or after it was returned from the database.
+func (m *ProxyMutation) ID() (id int64, exists bool) {
+ if m.id == nil {
+ return
+ }
+ return *m.id, true
+}
+
+// IDs queries the database and returns the entity ids that match the mutation's predicate.
+// That means, if the mutation is applied within a transaction with an isolation level such
+// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated
+// or updated by the mutation.
+func (m *ProxyMutation) IDs(ctx context.Context) ([]int64, error) {
+ switch {
+ case m.op.Is(OpUpdateOne | OpDeleteOne):
+ id, exists := m.ID()
+ if exists {
+ return []int64{id}, nil
+ }
+ fallthrough
+ case m.op.Is(OpUpdate | OpDelete):
+ return m.Client().Proxy.Query().Where(m.predicates...).IDs(ctx)
+ default:
+ return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op)
+ }
+}
+
+// SetCreatedAt sets the "created_at" field.
+func (m *ProxyMutation) SetCreatedAt(t time.Time) {
+ m.created_at = &t
+}
+
+// CreatedAt returns the value of the "created_at" field in the mutation.
+func (m *ProxyMutation) CreatedAt() (r time.Time, exists bool) {
+ v := m.created_at
+ if v == nil {
+ return
+ }
+ return *v, true
}
-// AddedFields returns all numeric fields that were incremented/decremented during
-// this mutation.
-func (m *PromoCodeMutation) AddedFields() []string {
- var fields []string
- if m.addbonus_amount != nil {
- fields = append(fields, promocode.FieldBonusAmount)
+// OldCreatedAt returns the old "created_at" field's value of the Proxy entity.
+// If the Proxy object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *ProxyMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations")
}
- if m.addmax_uses != nil {
- fields = append(fields, promocode.FieldMaxUses)
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldCreatedAt requires an ID field in the mutation")
}
- if m.addused_count != nil {
- fields = append(fields, promocode.FieldUsedCount)
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err)
}
- return fields
+ return oldValue.CreatedAt, nil
}
-// AddedField returns the numeric value that was incremented/decremented on a field
-// with the given name. The second boolean return value indicates that this field
-// was not set, or was not defined in the schema.
-func (m *PromoCodeMutation) AddedField(name string) (ent.Value, bool) {
- switch name {
- case promocode.FieldBonusAmount:
- return m.AddedBonusAmount()
- case promocode.FieldMaxUses:
- return m.AddedMaxUses()
- case promocode.FieldUsedCount:
- return m.AddedUsedCount()
- }
- return nil, false
+// ResetCreatedAt resets all changes to the "created_at" field.
+func (m *ProxyMutation) ResetCreatedAt() {
+ m.created_at = nil
}
-// AddField adds the value to the field with the given name. It returns an error if
-// the field is not defined in the schema, or if the type mismatched the field
-// type.
-func (m *PromoCodeMutation) AddField(name string, value ent.Value) error {
- switch name {
- case promocode.FieldBonusAmount:
- v, ok := value.(float64)
- if !ok {
- return fmt.Errorf("unexpected type %T for field %s", value, name)
- }
- m.AddBonusAmount(v)
- return nil
- case promocode.FieldMaxUses:
- v, ok := value.(int)
- if !ok {
- return fmt.Errorf("unexpected type %T for field %s", value, name)
- }
- m.AddMaxUses(v)
- return nil
- case promocode.FieldUsedCount:
- v, ok := value.(int)
- if !ok {
- return fmt.Errorf("unexpected type %T for field %s", value, name)
- }
- m.AddUsedCount(v)
- return nil
+// SetUpdatedAt sets the "updated_at" field.
+func (m *ProxyMutation) SetUpdatedAt(t time.Time) {
+ m.updated_at = &t
+}
+
+// UpdatedAt returns the value of the "updated_at" field in the mutation.
+func (m *ProxyMutation) UpdatedAt() (r time.Time, exists bool) {
+ v := m.updated_at
+ if v == nil {
+ return
}
- return fmt.Errorf("unknown PromoCode numeric field %s", name)
+ return *v, true
}
-// ClearedFields returns all nullable fields that were cleared during this
-// mutation.
-func (m *PromoCodeMutation) ClearedFields() []string {
- var fields []string
- if m.FieldCleared(promocode.FieldExpiresAt) {
- fields = append(fields, promocode.FieldExpiresAt)
+// OldUpdatedAt returns the old "updated_at" field's value of the Proxy entity.
+// If the Proxy object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *ProxyMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations")
}
- if m.FieldCleared(promocode.FieldNotes) {
- fields = append(fields, promocode.FieldNotes)
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldUpdatedAt requires an ID field in the mutation")
}
- return fields
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err)
+ }
+ return oldValue.UpdatedAt, nil
}
-// FieldCleared returns a boolean indicating if a field with the given name was
-// cleared in this mutation.
-func (m *PromoCodeMutation) FieldCleared(name string) bool {
- _, ok := m.clearedFields[name]
- return ok
+// ResetUpdatedAt resets all changes to the "updated_at" field.
+func (m *ProxyMutation) ResetUpdatedAt() {
+ m.updated_at = nil
}
-// ClearField clears the value of the field with the given name. It returns an
-// error if the field is not defined in the schema.
-func (m *PromoCodeMutation) ClearField(name string) error {
- switch name {
- case promocode.FieldExpiresAt:
- m.ClearExpiresAt()
- return nil
- case promocode.FieldNotes:
- m.ClearNotes()
- return nil
- }
- return fmt.Errorf("unknown PromoCode nullable field %s", name)
+// SetDeletedAt sets the "deleted_at" field.
+func (m *ProxyMutation) SetDeletedAt(t time.Time) {
+ m.deleted_at = &t
}
-// ResetField resets all changes in the mutation for the field with the given name.
-// It returns an error if the field is not defined in the schema.
-func (m *PromoCodeMutation) ResetField(name string) error {
- switch name {
- case promocode.FieldCode:
- m.ResetCode()
- return nil
- case promocode.FieldBonusAmount:
- m.ResetBonusAmount()
- return nil
- case promocode.FieldMaxUses:
- m.ResetMaxUses()
- return nil
- case promocode.FieldUsedCount:
- m.ResetUsedCount()
- return nil
- case promocode.FieldStatus:
- m.ResetStatus()
- return nil
- case promocode.FieldExpiresAt:
- m.ResetExpiresAt()
- return nil
- case promocode.FieldNotes:
- m.ResetNotes()
- return nil
- case promocode.FieldCreatedAt:
- m.ResetCreatedAt()
- return nil
- case promocode.FieldUpdatedAt:
- m.ResetUpdatedAt()
- return nil
+// DeletedAt returns the value of the "deleted_at" field in the mutation.
+func (m *ProxyMutation) DeletedAt() (r time.Time, exists bool) {
+ v := m.deleted_at
+ if v == nil {
+ return
}
- return fmt.Errorf("unknown PromoCode field %s", name)
+ return *v, true
}
-// AddedEdges returns all edge names that were set/added in this mutation.
-func (m *PromoCodeMutation) AddedEdges() []string {
- edges := make([]string, 0, 1)
- if m.usage_records != nil {
- edges = append(edges, promocode.EdgeUsageRecords)
+// OldDeletedAt returns the old "deleted_at" field's value of the Proxy entity.
+// If the Proxy object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *ProxyMutation) OldDeletedAt(ctx context.Context) (v *time.Time, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldDeletedAt is only allowed on UpdateOne operations")
}
- return edges
-}
-
-// AddedIDs returns all IDs (to other nodes) that were added for the given edge
-// name in this mutation.
-func (m *PromoCodeMutation) AddedIDs(name string) []ent.Value {
- switch name {
- case promocode.EdgeUsageRecords:
- ids := make([]ent.Value, 0, len(m.usage_records))
- for id := range m.usage_records {
- ids = append(ids, id)
- }
- return ids
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldDeletedAt requires an ID field in the mutation")
}
- return nil
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldDeletedAt: %w", err)
+ }
+ return oldValue.DeletedAt, nil
}
-// RemovedEdges returns all edge names that were removed in this mutation.
-func (m *PromoCodeMutation) RemovedEdges() []string {
- edges := make([]string, 0, 1)
- if m.removedusage_records != nil {
- edges = append(edges, promocode.EdgeUsageRecords)
- }
- return edges
+// ClearDeletedAt clears the value of the "deleted_at" field.
+func (m *ProxyMutation) ClearDeletedAt() {
+ m.deleted_at = nil
+ m.clearedFields[proxy.FieldDeletedAt] = struct{}{}
}
-// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with
-// the given name in this mutation.
-func (m *PromoCodeMutation) RemovedIDs(name string) []ent.Value {
- switch name {
- case promocode.EdgeUsageRecords:
- ids := make([]ent.Value, 0, len(m.removedusage_records))
- for id := range m.removedusage_records {
- ids = append(ids, id)
- }
- return ids
- }
- return nil
+// DeletedAtCleared returns if the "deleted_at" field was cleared in this mutation.
+func (m *ProxyMutation) DeletedAtCleared() bool {
+ _, ok := m.clearedFields[proxy.FieldDeletedAt]
+ return ok
}
-// ClearedEdges returns all edge names that were cleared in this mutation.
-func (m *PromoCodeMutation) ClearedEdges() []string {
- edges := make([]string, 0, 1)
- if m.clearedusage_records {
- edges = append(edges, promocode.EdgeUsageRecords)
- }
- return edges
+// ResetDeletedAt resets all changes to the "deleted_at" field.
+func (m *ProxyMutation) ResetDeletedAt() {
+ m.deleted_at = nil
+ delete(m.clearedFields, proxy.FieldDeletedAt)
}
-// EdgeCleared returns a boolean which indicates if the edge with the given name
-// was cleared in this mutation.
-func (m *PromoCodeMutation) EdgeCleared(name string) bool {
- switch name {
- case promocode.EdgeUsageRecords:
- return m.clearedusage_records
- }
- return false
+// SetName sets the "name" field.
+func (m *ProxyMutation) SetName(s string) {
+ m.name = &s
}
-// ClearEdge clears the value of the edge with the given name. It returns an error
-// if that edge is not defined in the schema.
-func (m *PromoCodeMutation) ClearEdge(name string) error {
- switch name {
+// Name returns the value of the "name" field in the mutation.
+func (m *ProxyMutation) Name() (r string, exists bool) {
+ v := m.name
+ if v == nil {
+ return
}
- return fmt.Errorf("unknown PromoCode unique edge %s", name)
+ return *v, true
}
-// ResetEdge resets all changes to the edge with the given name in this mutation.
-// It returns an error if the edge is not defined in the schema.
-func (m *PromoCodeMutation) ResetEdge(name string) error {
- switch name {
- case promocode.EdgeUsageRecords:
- m.ResetUsageRecords()
- return nil
+// OldName returns the old "name" field's value of the Proxy entity.
+// If the Proxy object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *ProxyMutation) OldName(ctx context.Context) (v string, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldName is only allowed on UpdateOne operations")
}
- return fmt.Errorf("unknown PromoCode edge %s", name)
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldName requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldName: %w", err)
+ }
+ return oldValue.Name, nil
}
-// PromoCodeUsageMutation represents an operation that mutates the PromoCodeUsage nodes in the graph.
-type PromoCodeUsageMutation struct {
- config
- op Op
- typ string
- id *int64
- bonus_amount *float64
- addbonus_amount *float64
- used_at *time.Time
- clearedFields map[string]struct{}
- promo_code *int64
- clearedpromo_code bool
- user *int64
- cleareduser bool
- done bool
- oldValue func(context.Context) (*PromoCodeUsage, error)
- predicates []predicate.PromoCodeUsage
+// ResetName resets all changes to the "name" field.
+func (m *ProxyMutation) ResetName() {
+ m.name = nil
}
-var _ ent.Mutation = (*PromoCodeUsageMutation)(nil)
-
-// promocodeusageOption allows management of the mutation configuration using functional options.
-type promocodeusageOption func(*PromoCodeUsageMutation)
-
-// newPromoCodeUsageMutation creates new mutation for the PromoCodeUsage entity.
-func newPromoCodeUsageMutation(c config, op Op, opts ...promocodeusageOption) *PromoCodeUsageMutation {
- m := &PromoCodeUsageMutation{
- config: c,
- op: op,
- typ: TypePromoCodeUsage,
- clearedFields: make(map[string]struct{}),
- }
- for _, opt := range opts {
- opt(m)
- }
- return m
+// SetProtocol sets the "protocol" field.
+func (m *ProxyMutation) SetProtocol(s string) {
+ m.protocol = &s
}
-// withPromoCodeUsageID sets the ID field of the mutation.
-func withPromoCodeUsageID(id int64) promocodeusageOption {
- return func(m *PromoCodeUsageMutation) {
- var (
- err error
- once sync.Once
- value *PromoCodeUsage
- )
- m.oldValue = func(ctx context.Context) (*PromoCodeUsage, error) {
- once.Do(func() {
- if m.done {
- err = errors.New("querying old values post mutation is not allowed")
- } else {
- value, err = m.Client().PromoCodeUsage.Get(ctx, id)
- }
- })
- return value, err
- }
- m.id = &id
+// Protocol returns the value of the "protocol" field in the mutation.
+func (m *ProxyMutation) Protocol() (r string, exists bool) {
+ v := m.protocol
+ if v == nil {
+ return
}
+ return *v, true
}
-// withPromoCodeUsage sets the old PromoCodeUsage of the mutation.
-func withPromoCodeUsage(node *PromoCodeUsage) promocodeusageOption {
- return func(m *PromoCodeUsageMutation) {
- m.oldValue = func(context.Context) (*PromoCodeUsage, error) {
- return node, nil
- }
- m.id = &node.ID
+// OldProtocol returns the old "protocol" field's value of the Proxy entity.
+// If the Proxy object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *ProxyMutation) OldProtocol(ctx context.Context) (v string, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldProtocol is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldProtocol requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldProtocol: %w", err)
}
+ return oldValue.Protocol, nil
}
-// Client returns a new `ent.Client` from the mutation. If the mutation was
-// executed in a transaction (ent.Tx), a transactional client is returned.
-func (m PromoCodeUsageMutation) Client() *Client {
- client := &Client{config: m.config}
- client.init()
- return client
+// ResetProtocol resets all changes to the "protocol" field.
+func (m *ProxyMutation) ResetProtocol() {
+ m.protocol = nil
}
-// Tx returns an `ent.Tx` for mutations that were executed in transactions;
-// it returns an error otherwise.
-func (m PromoCodeUsageMutation) Tx() (*Tx, error) {
- if _, ok := m.driver.(*txDriver); !ok {
- return nil, errors.New("ent: mutation is not running in a transaction")
- }
- tx := &Tx{config: m.config}
- tx.init()
- return tx, nil
+// SetHost sets the "host" field.
+func (m *ProxyMutation) SetHost(s string) {
+ m.host = &s
}
-// ID returns the ID value in the mutation. Note that the ID is only available
-// if it was provided to the builder or after it was returned from the database.
-func (m *PromoCodeUsageMutation) ID() (id int64, exists bool) {
- if m.id == nil {
+// Host returns the value of the "host" field in the mutation.
+func (m *ProxyMutation) Host() (r string, exists bool) {
+ v := m.host
+ if v == nil {
return
}
- return *m.id, true
+ return *v, true
}
-// IDs queries the database and returns the entity ids that match the mutation's predicate.
-// That means, if the mutation is applied within a transaction with an isolation level such
-// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated
-// or updated by the mutation.
-func (m *PromoCodeUsageMutation) IDs(ctx context.Context) ([]int64, error) {
- switch {
- case m.op.Is(OpUpdateOne | OpDeleteOne):
- id, exists := m.ID()
- if exists {
- return []int64{id}, nil
- }
- fallthrough
- case m.op.Is(OpUpdate | OpDelete):
- return m.Client().PromoCodeUsage.Query().Where(m.predicates...).IDs(ctx)
- default:
- return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op)
+// OldHost returns the old "host" field's value of the Proxy entity.
+// If the Proxy object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *ProxyMutation) OldHost(ctx context.Context) (v string, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldHost is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldHost requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldHost: %w", err)
}
+ return oldValue.Host, nil
}
-// SetPromoCodeID sets the "promo_code_id" field.
-func (m *PromoCodeUsageMutation) SetPromoCodeID(i int64) {
- m.promo_code = &i
+// ResetHost resets all changes to the "host" field.
+func (m *ProxyMutation) ResetHost() {
+ m.host = nil
}
-// PromoCodeID returns the value of the "promo_code_id" field in the mutation.
-func (m *PromoCodeUsageMutation) PromoCodeID() (r int64, exists bool) {
- v := m.promo_code
+// SetPort sets the "port" field.
+func (m *ProxyMutation) SetPort(i int) {
+ m.port = &i
+ m.addport = nil
+}
+
+// Port returns the value of the "port" field in the mutation.
+func (m *ProxyMutation) Port() (r int, exists bool) {
+ v := m.port
if v == nil {
return
}
return *v, true
}
-// OldPromoCodeID returns the old "promo_code_id" field's value of the PromoCodeUsage entity.
-// If the PromoCodeUsage object wasn't provided to the builder, the object is fetched from the database.
+// OldPort returns the old "port" field's value of the Proxy entity.
+// If the Proxy object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
-func (m *PromoCodeUsageMutation) OldPromoCodeID(ctx context.Context) (v int64, err error) {
+func (m *ProxyMutation) OldPort(ctx context.Context) (v int, err error) {
if !m.op.Is(OpUpdateOne) {
- return v, errors.New("OldPromoCodeID is only allowed on UpdateOne operations")
+ return v, errors.New("OldPort is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
- return v, errors.New("OldPromoCodeID requires an ID field in the mutation")
+ return v, errors.New("OldPort requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
- return v, fmt.Errorf("querying old value for OldPromoCodeID: %w", err)
+ return v, fmt.Errorf("querying old value for OldPort: %w", err)
}
- return oldValue.PromoCodeID, nil
+ return oldValue.Port, nil
}
-// ResetPromoCodeID resets all changes to the "promo_code_id" field.
-func (m *PromoCodeUsageMutation) ResetPromoCodeID() {
- m.promo_code = nil
+// AddPort adds i to the "port" field.
+func (m *ProxyMutation) AddPort(i int) {
+ if m.addport != nil {
+ *m.addport += i
+ } else {
+ m.addport = &i
+ }
}
-// SetUserID sets the "user_id" field.
-func (m *PromoCodeUsageMutation) SetUserID(i int64) {
- m.user = &i
+// AddedPort returns the value that was added to the "port" field in this mutation.
+func (m *ProxyMutation) AddedPort() (r int, exists bool) {
+ v := m.addport
+ if v == nil {
+ return
+ }
+ return *v, true
}
-// UserID returns the value of the "user_id" field in the mutation.
-func (m *PromoCodeUsageMutation) UserID() (r int64, exists bool) {
- v := m.user
+// ResetPort resets all changes to the "port" field.
+func (m *ProxyMutation) ResetPort() {
+ m.port = nil
+ m.addport = nil
+}
+
+// SetUsername sets the "username" field.
+func (m *ProxyMutation) SetUsername(s string) {
+ m.username = &s
+}
+
+// Username returns the value of the "username" field in the mutation.
+func (m *ProxyMutation) Username() (r string, exists bool) {
+ v := m.username
if v == nil {
return
}
return *v, true
}
-// OldUserID returns the old "user_id" field's value of the PromoCodeUsage entity.
-// If the PromoCodeUsage object wasn't provided to the builder, the object is fetched from the database.
+// OldUsername returns the old "username" field's value of the Proxy entity.
+// If the Proxy object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
-func (m *PromoCodeUsageMutation) OldUserID(ctx context.Context) (v int64, err error) {
+func (m *ProxyMutation) OldUsername(ctx context.Context) (v *string, err error) {
if !m.op.Is(OpUpdateOne) {
- return v, errors.New("OldUserID is only allowed on UpdateOne operations")
+ return v, errors.New("OldUsername is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
- return v, errors.New("OldUserID requires an ID field in the mutation")
+ return v, errors.New("OldUsername requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
- return v, fmt.Errorf("querying old value for OldUserID: %w", err)
+ return v, fmt.Errorf("querying old value for OldUsername: %w", err)
}
- return oldValue.UserID, nil
+ return oldValue.Username, nil
+}
+
+// ClearUsername clears the value of the "username" field.
+func (m *ProxyMutation) ClearUsername() {
+ m.username = nil
+ m.clearedFields[proxy.FieldUsername] = struct{}{}
}
-// ResetUserID resets all changes to the "user_id" field.
-func (m *PromoCodeUsageMutation) ResetUserID() {
- m.user = nil
+// UsernameCleared returns if the "username" field was cleared in this mutation.
+func (m *ProxyMutation) UsernameCleared() bool {
+ _, ok := m.clearedFields[proxy.FieldUsername]
+ return ok
}
-// SetBonusAmount sets the "bonus_amount" field.
-func (m *PromoCodeUsageMutation) SetBonusAmount(f float64) {
- m.bonus_amount = &f
- m.addbonus_amount = nil
+// ResetUsername resets all changes to the "username" field.
+func (m *ProxyMutation) ResetUsername() {
+ m.username = nil
+ delete(m.clearedFields, proxy.FieldUsername)
}
-// BonusAmount returns the value of the "bonus_amount" field in the mutation.
-func (m *PromoCodeUsageMutation) BonusAmount() (r float64, exists bool) {
- v := m.bonus_amount
+// SetPassword sets the "password" field.
+func (m *ProxyMutation) SetPassword(s string) {
+ m.password = &s
+}
+
+// Password returns the value of the "password" field in the mutation.
+func (m *ProxyMutation) Password() (r string, exists bool) {
+ v := m.password
if v == nil {
return
}
return *v, true
}
-// OldBonusAmount returns the old "bonus_amount" field's value of the PromoCodeUsage entity.
-// If the PromoCodeUsage object wasn't provided to the builder, the object is fetched from the database.
+// OldPassword returns the old "password" field's value of the Proxy entity.
+// If the Proxy object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
-func (m *PromoCodeUsageMutation) OldBonusAmount(ctx context.Context) (v float64, err error) {
+func (m *ProxyMutation) OldPassword(ctx context.Context) (v *string, err error) {
if !m.op.Is(OpUpdateOne) {
- return v, errors.New("OldBonusAmount is only allowed on UpdateOne operations")
+ return v, errors.New("OldPassword is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
- return v, errors.New("OldBonusAmount requires an ID field in the mutation")
+ return v, errors.New("OldPassword requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
- return v, fmt.Errorf("querying old value for OldBonusAmount: %w", err)
+ return v, fmt.Errorf("querying old value for OldPassword: %w", err)
}
- return oldValue.BonusAmount, nil
+ return oldValue.Password, nil
}
-// AddBonusAmount adds f to the "bonus_amount" field.
-func (m *PromoCodeUsageMutation) AddBonusAmount(f float64) {
- if m.addbonus_amount != nil {
- *m.addbonus_amount += f
- } else {
- m.addbonus_amount = &f
- }
+// ClearPassword clears the value of the "password" field.
+func (m *ProxyMutation) ClearPassword() {
+ m.password = nil
+ m.clearedFields[proxy.FieldPassword] = struct{}{}
}
-// AddedBonusAmount returns the value that was added to the "bonus_amount" field in this mutation.
-func (m *PromoCodeUsageMutation) AddedBonusAmount() (r float64, exists bool) {
- v := m.addbonus_amount
- if v == nil {
- return
- }
- return *v, true
+// PasswordCleared returns if the "password" field was cleared in this mutation.
+func (m *ProxyMutation) PasswordCleared() bool {
+ _, ok := m.clearedFields[proxy.FieldPassword]
+ return ok
}
-// ResetBonusAmount resets all changes to the "bonus_amount" field.
-func (m *PromoCodeUsageMutation) ResetBonusAmount() {
- m.bonus_amount = nil
- m.addbonus_amount = nil
+// ResetPassword resets all changes to the "password" field.
+func (m *ProxyMutation) ResetPassword() {
+ m.password = nil
+ delete(m.clearedFields, proxy.FieldPassword)
}
-// SetUsedAt sets the "used_at" field.
-func (m *PromoCodeUsageMutation) SetUsedAt(t time.Time) {
- m.used_at = &t
+// SetStatus sets the "status" field.
+func (m *ProxyMutation) SetStatus(s string) {
+ m.status = &s
}
-// UsedAt returns the value of the "used_at" field in the mutation.
-func (m *PromoCodeUsageMutation) UsedAt() (r time.Time, exists bool) {
- v := m.used_at
+// Status returns the value of the "status" field in the mutation.
+func (m *ProxyMutation) Status() (r string, exists bool) {
+ v := m.status
if v == nil {
return
}
return *v, true
}
-// OldUsedAt returns the old "used_at" field's value of the PromoCodeUsage entity.
-// If the PromoCodeUsage object wasn't provided to the builder, the object is fetched from the database.
+// OldStatus returns the old "status" field's value of the Proxy entity.
+// If the Proxy object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
-func (m *PromoCodeUsageMutation) OldUsedAt(ctx context.Context) (v time.Time, err error) {
+func (m *ProxyMutation) OldStatus(ctx context.Context) (v string, err error) {
if !m.op.Is(OpUpdateOne) {
- return v, errors.New("OldUsedAt is only allowed on UpdateOne operations")
+ return v, errors.New("OldStatus is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
- return v, errors.New("OldUsedAt requires an ID field in the mutation")
+ return v, errors.New("OldStatus requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
- return v, fmt.Errorf("querying old value for OldUsedAt: %w", err)
+ return v, fmt.Errorf("querying old value for OldStatus: %w", err)
}
- return oldValue.UsedAt, nil
-}
-
-// ResetUsedAt resets all changes to the "used_at" field.
-func (m *PromoCodeUsageMutation) ResetUsedAt() {
- m.used_at = nil
+ return oldValue.Status, nil
}
-// ClearPromoCode clears the "promo_code" edge to the PromoCode entity.
-func (m *PromoCodeUsageMutation) ClearPromoCode() {
- m.clearedpromo_code = true
- m.clearedFields[promocodeusage.FieldPromoCodeID] = struct{}{}
+// ResetStatus resets all changes to the "status" field.
+func (m *ProxyMutation) ResetStatus() {
+ m.status = nil
}
-// PromoCodeCleared reports if the "promo_code" edge to the PromoCode entity was cleared.
-func (m *PromoCodeUsageMutation) PromoCodeCleared() bool {
- return m.clearedpromo_code
+// AddAccountIDs adds the "accounts" edge to the Account entity by ids.
+func (m *ProxyMutation) AddAccountIDs(ids ...int64) {
+ if m.accounts == nil {
+ m.accounts = make(map[int64]struct{})
+ }
+ for i := range ids {
+ m.accounts[ids[i]] = struct{}{}
+ }
}
-// PromoCodeIDs returns the "promo_code" edge IDs in the mutation.
-// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use
-// PromoCodeID instead. It exists only for internal usage by the builders.
-func (m *PromoCodeUsageMutation) PromoCodeIDs() (ids []int64) {
- if id := m.promo_code; id != nil {
- ids = append(ids, *id)
- }
- return
+// ClearAccounts clears the "accounts" edge to the Account entity.
+func (m *ProxyMutation) ClearAccounts() {
+ m.clearedaccounts = true
}
-// ResetPromoCode resets all changes to the "promo_code" edge.
-func (m *PromoCodeUsageMutation) ResetPromoCode() {
- m.promo_code = nil
- m.clearedpromo_code = false
+// AccountsCleared reports if the "accounts" edge to the Account entity was cleared.
+func (m *ProxyMutation) AccountsCleared() bool {
+ return m.clearedaccounts
}
-// ClearUser clears the "user" edge to the User entity.
-func (m *PromoCodeUsageMutation) ClearUser() {
- m.cleareduser = true
- m.clearedFields[promocodeusage.FieldUserID] = struct{}{}
+// RemoveAccountIDs removes the "accounts" edge to the Account entity by IDs.
+func (m *ProxyMutation) RemoveAccountIDs(ids ...int64) {
+ if m.removedaccounts == nil {
+ m.removedaccounts = make(map[int64]struct{})
+ }
+ for i := range ids {
+ delete(m.accounts, ids[i])
+ m.removedaccounts[ids[i]] = struct{}{}
+ }
}
-// UserCleared reports if the "user" edge to the User entity was cleared.
-func (m *PromoCodeUsageMutation) UserCleared() bool {
- return m.cleareduser
+// RemovedAccounts returns the removed IDs of the "accounts" edge to the Account entity.
+func (m *ProxyMutation) RemovedAccountsIDs() (ids []int64) {
+ for id := range m.removedaccounts {
+ ids = append(ids, id)
+ }
+ return
}
-// UserIDs returns the "user" edge IDs in the mutation.
-// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use
-// UserID instead. It exists only for internal usage by the builders.
-func (m *PromoCodeUsageMutation) UserIDs() (ids []int64) {
- if id := m.user; id != nil {
- ids = append(ids, *id)
+// AccountsIDs returns the "accounts" edge IDs in the mutation.
+func (m *ProxyMutation) AccountsIDs() (ids []int64) {
+ for id := range m.accounts {
+ ids = append(ids, id)
}
return
}
-// ResetUser resets all changes to the "user" edge.
-func (m *PromoCodeUsageMutation) ResetUser() {
- m.user = nil
- m.cleareduser = false
+// ResetAccounts resets all changes to the "accounts" edge.
+func (m *ProxyMutation) ResetAccounts() {
+ m.accounts = nil
+ m.clearedaccounts = false
+ m.removedaccounts = nil
}
-// Where appends a list predicates to the PromoCodeUsageMutation builder.
-func (m *PromoCodeUsageMutation) Where(ps ...predicate.PromoCodeUsage) {
+// Where appends a list predicates to the ProxyMutation builder.
+func (m *ProxyMutation) Where(ps ...predicate.Proxy) {
m.predicates = append(m.predicates, ps...)
}
-// WhereP appends storage-level predicates to the PromoCodeUsageMutation builder. Using this method,
+// WhereP appends storage-level predicates to the ProxyMutation builder. Using this method,
// users can use type-assertion to append predicates that do not depend on any generated package.
-func (m *PromoCodeUsageMutation) WhereP(ps ...func(*sql.Selector)) {
- p := make([]predicate.PromoCodeUsage, len(ps))
+func (m *ProxyMutation) WhereP(ps ...func(*sql.Selector)) {
+ p := make([]predicate.Proxy, len(ps))
for i := range ps {
p[i] = ps[i]
}
@@ -13474,36 +18772,54 @@ func (m *PromoCodeUsageMutation) WhereP(ps ...func(*sql.Selector)) {
}
// Op returns the operation name.
-func (m *PromoCodeUsageMutation) Op() Op {
+func (m *ProxyMutation) Op() Op {
return m.op
}
// SetOp allows setting the mutation operation.
-func (m *PromoCodeUsageMutation) SetOp(op Op) {
+func (m *ProxyMutation) SetOp(op Op) {
m.op = op
}
-// Type returns the node type of this mutation (PromoCodeUsage).
-func (m *PromoCodeUsageMutation) Type() string {
+// Type returns the node type of this mutation (Proxy).
+func (m *ProxyMutation) Type() string {
return m.typ
}
// Fields returns all fields that were changed during this mutation. Note that in
// order to get all numeric fields that were incremented/decremented, call
// AddedFields().
-func (m *PromoCodeUsageMutation) Fields() []string {
- fields := make([]string, 0, 4)
- if m.promo_code != nil {
- fields = append(fields, promocodeusage.FieldPromoCodeID)
+func (m *ProxyMutation) Fields() []string {
+ fields := make([]string, 0, 10)
+ if m.created_at != nil {
+ fields = append(fields, proxy.FieldCreatedAt)
}
- if m.user != nil {
- fields = append(fields, promocodeusage.FieldUserID)
+ if m.updated_at != nil {
+ fields = append(fields, proxy.FieldUpdatedAt)
}
- if m.bonus_amount != nil {
- fields = append(fields, promocodeusage.FieldBonusAmount)
+ if m.deleted_at != nil {
+ fields = append(fields, proxy.FieldDeletedAt)
}
- if m.used_at != nil {
- fields = append(fields, promocodeusage.FieldUsedAt)
+ if m.name != nil {
+ fields = append(fields, proxy.FieldName)
+ }
+ if m.protocol != nil {
+ fields = append(fields, proxy.FieldProtocol)
+ }
+ if m.host != nil {
+ fields = append(fields, proxy.FieldHost)
+ }
+ if m.port != nil {
+ fields = append(fields, proxy.FieldPort)
+ }
+ if m.username != nil {
+ fields = append(fields, proxy.FieldUsername)
+ }
+ if m.password != nil {
+ fields = append(fields, proxy.FieldPassword)
+ }
+ if m.status != nil {
+ fields = append(fields, proxy.FieldStatus)
}
return fields
}
@@ -13511,16 +18827,28 @@ func (m *PromoCodeUsageMutation) Fields() []string {
// Field returns the value of a field with the given name. The second boolean
// return value indicates that this field was not set, or was not defined in the
// schema.
-func (m *PromoCodeUsageMutation) Field(name string) (ent.Value, bool) {
+func (m *ProxyMutation) Field(name string) (ent.Value, bool) {
switch name {
- case promocodeusage.FieldPromoCodeID:
- return m.PromoCodeID()
- case promocodeusage.FieldUserID:
- return m.UserID()
- case promocodeusage.FieldBonusAmount:
- return m.BonusAmount()
- case promocodeusage.FieldUsedAt:
- return m.UsedAt()
+ case proxy.FieldCreatedAt:
+ return m.CreatedAt()
+ case proxy.FieldUpdatedAt:
+ return m.UpdatedAt()
+ case proxy.FieldDeletedAt:
+ return m.DeletedAt()
+ case proxy.FieldName:
+ return m.Name()
+ case proxy.FieldProtocol:
+ return m.Protocol()
+ case proxy.FieldHost:
+ return m.Host()
+ case proxy.FieldPort:
+ return m.Port()
+ case proxy.FieldUsername:
+ return m.Username()
+ case proxy.FieldPassword:
+ return m.Password()
+ case proxy.FieldStatus:
+ return m.Status()
}
return nil, false
}
@@ -13528,63 +18856,117 @@ func (m *PromoCodeUsageMutation) Field(name string) (ent.Value, bool) {
// OldField returns the old value of the field from the database. An error is
// returned if the mutation operation is not UpdateOne, or the query to the
// database failed.
-func (m *PromoCodeUsageMutation) OldField(ctx context.Context, name string) (ent.Value, error) {
+func (m *ProxyMutation) OldField(ctx context.Context, name string) (ent.Value, error) {
switch name {
- case promocodeusage.FieldPromoCodeID:
- return m.OldPromoCodeID(ctx)
- case promocodeusage.FieldUserID:
- return m.OldUserID(ctx)
- case promocodeusage.FieldBonusAmount:
- return m.OldBonusAmount(ctx)
- case promocodeusage.FieldUsedAt:
- return m.OldUsedAt(ctx)
+ case proxy.FieldCreatedAt:
+ return m.OldCreatedAt(ctx)
+ case proxy.FieldUpdatedAt:
+ return m.OldUpdatedAt(ctx)
+ case proxy.FieldDeletedAt:
+ return m.OldDeletedAt(ctx)
+ case proxy.FieldName:
+ return m.OldName(ctx)
+ case proxy.FieldProtocol:
+ return m.OldProtocol(ctx)
+ case proxy.FieldHost:
+ return m.OldHost(ctx)
+ case proxy.FieldPort:
+ return m.OldPort(ctx)
+ case proxy.FieldUsername:
+ return m.OldUsername(ctx)
+ case proxy.FieldPassword:
+ return m.OldPassword(ctx)
+ case proxy.FieldStatus:
+ return m.OldStatus(ctx)
}
- return nil, fmt.Errorf("unknown PromoCodeUsage field %s", name)
+ return nil, fmt.Errorf("unknown Proxy field %s", name)
}
// SetField sets the value of a field with the given name. It returns an error if
// the field is not defined in the schema, or if the type mismatched the field
// type.
-func (m *PromoCodeUsageMutation) SetField(name string, value ent.Value) error {
+func (m *ProxyMutation) SetField(name string, value ent.Value) error {
switch name {
- case promocodeusage.FieldPromoCodeID:
- v, ok := value.(int64)
+ case proxy.FieldCreatedAt:
+ v, ok := value.(time.Time)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
- m.SetPromoCodeID(v)
+ m.SetCreatedAt(v)
return nil
- case promocodeusage.FieldUserID:
- v, ok := value.(int64)
+ case proxy.FieldUpdatedAt:
+ v, ok := value.(time.Time)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
- m.SetUserID(v)
+ m.SetUpdatedAt(v)
return nil
- case promocodeusage.FieldBonusAmount:
- v, ok := value.(float64)
+ case proxy.FieldDeletedAt:
+ v, ok := value.(time.Time)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
- m.SetBonusAmount(v)
+ m.SetDeletedAt(v)
return nil
- case promocodeusage.FieldUsedAt:
- v, ok := value.(time.Time)
+ case proxy.FieldName:
+ v, ok := value.(string)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
- m.SetUsedAt(v)
+ m.SetName(v)
+ return nil
+ case proxy.FieldProtocol:
+ v, ok := value.(string)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetProtocol(v)
+ return nil
+ case proxy.FieldHost:
+ v, ok := value.(string)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetHost(v)
+ return nil
+ case proxy.FieldPort:
+ v, ok := value.(int)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetPort(v)
+ return nil
+ case proxy.FieldUsername:
+ v, ok := value.(string)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetUsername(v)
+ return nil
+ case proxy.FieldPassword:
+ v, ok := value.(string)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetPassword(v)
+ return nil
+ case proxy.FieldStatus:
+ v, ok := value.(string)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetStatus(v)
return nil
}
- return fmt.Errorf("unknown PromoCodeUsage field %s", name)
+ return fmt.Errorf("unknown Proxy field %s", name)
}
// AddedFields returns all numeric fields that were incremented/decremented during
// this mutation.
-func (m *PromoCodeUsageMutation) AddedFields() []string {
+func (m *ProxyMutation) AddedFields() []string {
var fields []string
- if m.addbonus_amount != nil {
- fields = append(fields, promocodeusage.FieldBonusAmount)
+ if m.addport != nil {
+ fields = append(fields, proxy.FieldPort)
}
return fields
}
@@ -13592,10 +18974,10 @@ func (m *PromoCodeUsageMutation) AddedFields() []string {
// AddedField returns the numeric value that was incremented/decremented on a field
// with the given name. The second boolean return value indicates that this field
// was not set, or was not defined in the schema.
-func (m *PromoCodeUsageMutation) AddedField(name string) (ent.Value, bool) {
+func (m *ProxyMutation) AddedField(name string) (ent.Value, bool) {
switch name {
- case promocodeusage.FieldBonusAmount:
- return m.AddedBonusAmount()
+ case proxy.FieldPort:
+ return m.AddedPort()
}
return nil, false
}
@@ -13603,187 +18985,218 @@ func (m *PromoCodeUsageMutation) AddedField(name string) (ent.Value, bool) {
// AddField adds the value to the field with the given name. It returns an error if
// the field is not defined in the schema, or if the type mismatched the field
// type.
-func (m *PromoCodeUsageMutation) AddField(name string, value ent.Value) error {
+func (m *ProxyMutation) AddField(name string, value ent.Value) error {
switch name {
- case promocodeusage.FieldBonusAmount:
- v, ok := value.(float64)
+ case proxy.FieldPort:
+ v, ok := value.(int)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
- m.AddBonusAmount(v)
+ m.AddPort(v)
return nil
}
- return fmt.Errorf("unknown PromoCodeUsage numeric field %s", name)
+ return fmt.Errorf("unknown Proxy numeric field %s", name)
}
// ClearedFields returns all nullable fields that were cleared during this
// mutation.
-func (m *PromoCodeUsageMutation) ClearedFields() []string {
- return nil
+func (m *ProxyMutation) ClearedFields() []string {
+ var fields []string
+ if m.FieldCleared(proxy.FieldDeletedAt) {
+ fields = append(fields, proxy.FieldDeletedAt)
+ }
+ if m.FieldCleared(proxy.FieldUsername) {
+ fields = append(fields, proxy.FieldUsername)
+ }
+ if m.FieldCleared(proxy.FieldPassword) {
+ fields = append(fields, proxy.FieldPassword)
+ }
+ return fields
}
// FieldCleared returns a boolean indicating if a field with the given name was
// cleared in this mutation.
-func (m *PromoCodeUsageMutation) FieldCleared(name string) bool {
+func (m *ProxyMutation) FieldCleared(name string) bool {
_, ok := m.clearedFields[name]
return ok
}
// ClearField clears the value of the field with the given name. It returns an
// error if the field is not defined in the schema.
-func (m *PromoCodeUsageMutation) ClearField(name string) error {
- return fmt.Errorf("unknown PromoCodeUsage nullable field %s", name)
+func (m *ProxyMutation) ClearField(name string) error {
+ switch name {
+ case proxy.FieldDeletedAt:
+ m.ClearDeletedAt()
+ return nil
+ case proxy.FieldUsername:
+ m.ClearUsername()
+ return nil
+ case proxy.FieldPassword:
+ m.ClearPassword()
+ return nil
+ }
+ return fmt.Errorf("unknown Proxy nullable field %s", name)
}
// ResetField resets all changes in the mutation for the field with the given name.
// It returns an error if the field is not defined in the schema.
-func (m *PromoCodeUsageMutation) ResetField(name string) error {
+func (m *ProxyMutation) ResetField(name string) error {
switch name {
- case promocodeusage.FieldPromoCodeID:
- m.ResetPromoCodeID()
+ case proxy.FieldCreatedAt:
+ m.ResetCreatedAt()
return nil
- case promocodeusage.FieldUserID:
- m.ResetUserID()
+ case proxy.FieldUpdatedAt:
+ m.ResetUpdatedAt()
return nil
- case promocodeusage.FieldBonusAmount:
- m.ResetBonusAmount()
+ case proxy.FieldDeletedAt:
+ m.ResetDeletedAt()
return nil
- case promocodeusage.FieldUsedAt:
- m.ResetUsedAt()
+ case proxy.FieldName:
+ m.ResetName()
+ return nil
+ case proxy.FieldProtocol:
+ m.ResetProtocol()
+ return nil
+ case proxy.FieldHost:
+ m.ResetHost()
+ return nil
+ case proxy.FieldPort:
+ m.ResetPort()
+ return nil
+ case proxy.FieldUsername:
+ m.ResetUsername()
+ return nil
+ case proxy.FieldPassword:
+ m.ResetPassword()
+ return nil
+ case proxy.FieldStatus:
+ m.ResetStatus()
return nil
}
- return fmt.Errorf("unknown PromoCodeUsage field %s", name)
+ return fmt.Errorf("unknown Proxy field %s", name)
}
// AddedEdges returns all edge names that were set/added in this mutation.
-func (m *PromoCodeUsageMutation) AddedEdges() []string {
- edges := make([]string, 0, 2)
- if m.promo_code != nil {
- edges = append(edges, promocodeusage.EdgePromoCode)
- }
- if m.user != nil {
- edges = append(edges, promocodeusage.EdgeUser)
+func (m *ProxyMutation) AddedEdges() []string {
+ edges := make([]string, 0, 1)
+ if m.accounts != nil {
+ edges = append(edges, proxy.EdgeAccounts)
}
return edges
}
// AddedIDs returns all IDs (to other nodes) that were added for the given edge
// name in this mutation.
-func (m *PromoCodeUsageMutation) AddedIDs(name string) []ent.Value {
+func (m *ProxyMutation) AddedIDs(name string) []ent.Value {
switch name {
- case promocodeusage.EdgePromoCode:
- if id := m.promo_code; id != nil {
- return []ent.Value{*id}
- }
- case promocodeusage.EdgeUser:
- if id := m.user; id != nil {
- return []ent.Value{*id}
+ case proxy.EdgeAccounts:
+ ids := make([]ent.Value, 0, len(m.accounts))
+ for id := range m.accounts {
+ ids = append(ids, id)
}
+ return ids
}
return nil
}
// RemovedEdges returns all edge names that were removed in this mutation.
-func (m *PromoCodeUsageMutation) RemovedEdges() []string {
- edges := make([]string, 0, 2)
+func (m *ProxyMutation) RemovedEdges() []string {
+ edges := make([]string, 0, 1)
+ if m.removedaccounts != nil {
+ edges = append(edges, proxy.EdgeAccounts)
+ }
return edges
}
// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with
// the given name in this mutation.
-func (m *PromoCodeUsageMutation) RemovedIDs(name string) []ent.Value {
+func (m *ProxyMutation) RemovedIDs(name string) []ent.Value {
+ switch name {
+ case proxy.EdgeAccounts:
+ ids := make([]ent.Value, 0, len(m.removedaccounts))
+ for id := range m.removedaccounts {
+ ids = append(ids, id)
+ }
+ return ids
+ }
return nil
}
// ClearedEdges returns all edge names that were cleared in this mutation.
-func (m *PromoCodeUsageMutation) ClearedEdges() []string {
- edges := make([]string, 0, 2)
- if m.clearedpromo_code {
- edges = append(edges, promocodeusage.EdgePromoCode)
- }
- if m.cleareduser {
- edges = append(edges, promocodeusage.EdgeUser)
+func (m *ProxyMutation) ClearedEdges() []string {
+ edges := make([]string, 0, 1)
+ if m.clearedaccounts {
+ edges = append(edges, proxy.EdgeAccounts)
}
return edges
}
// EdgeCleared returns a boolean which indicates if the edge with the given name
// was cleared in this mutation.
-func (m *PromoCodeUsageMutation) EdgeCleared(name string) bool {
+func (m *ProxyMutation) EdgeCleared(name string) bool {
switch name {
- case promocodeusage.EdgePromoCode:
- return m.clearedpromo_code
- case promocodeusage.EdgeUser:
- return m.cleareduser
+ case proxy.EdgeAccounts:
+ return m.clearedaccounts
}
return false
}
// ClearEdge clears the value of the edge with the given name. It returns an error
// if that edge is not defined in the schema.
-func (m *PromoCodeUsageMutation) ClearEdge(name string) error {
+func (m *ProxyMutation) ClearEdge(name string) error {
switch name {
- case promocodeusage.EdgePromoCode:
- m.ClearPromoCode()
- return nil
- case promocodeusage.EdgeUser:
- m.ClearUser()
- return nil
}
- return fmt.Errorf("unknown PromoCodeUsage unique edge %s", name)
+ return fmt.Errorf("unknown Proxy unique edge %s", name)
}
// ResetEdge resets all changes to the edge with the given name in this mutation.
// It returns an error if the edge is not defined in the schema.
-func (m *PromoCodeUsageMutation) ResetEdge(name string) error {
+func (m *ProxyMutation) ResetEdge(name string) error {
switch name {
- case promocodeusage.EdgePromoCode:
- m.ResetPromoCode()
- return nil
- case promocodeusage.EdgeUser:
- m.ResetUser()
+ case proxy.EdgeAccounts:
+ m.ResetAccounts()
return nil
}
- return fmt.Errorf("unknown PromoCodeUsage edge %s", name)
+ return fmt.Errorf("unknown Proxy edge %s", name)
}
-// ProxyMutation represents an operation that mutates the Proxy nodes in the graph.
-type ProxyMutation struct {
+// RedeemCodeMutation represents an operation that mutates the RedeemCode nodes in the graph.
+type RedeemCodeMutation struct {
config
- op Op
- typ string
- id *int64
- created_at *time.Time
- updated_at *time.Time
- deleted_at *time.Time
- name *string
- protocol *string
- host *string
- port *int
- addport *int
- username *string
- password *string
- status *string
- clearedFields map[string]struct{}
- accounts map[int64]struct{}
- removedaccounts map[int64]struct{}
- clearedaccounts bool
- done bool
- oldValue func(context.Context) (*Proxy, error)
- predicates []predicate.Proxy
+ op Op
+ typ string
+ id *int64
+ code *string
+ _type *string
+ value *float64
+ addvalue *float64
+ status *string
+ used_at *time.Time
+ notes *string
+ created_at *time.Time
+ validity_days *int
+ addvalidity_days *int
+ clearedFields map[string]struct{}
+ user *int64
+ cleareduser bool
+ group *int64
+ clearedgroup bool
+ done bool
+ oldValue func(context.Context) (*RedeemCode, error)
+ predicates []predicate.RedeemCode
}
-var _ ent.Mutation = (*ProxyMutation)(nil)
+var _ ent.Mutation = (*RedeemCodeMutation)(nil)
-// proxyOption allows management of the mutation configuration using functional options.
-type proxyOption func(*ProxyMutation)
+// redeemcodeOption allows management of the mutation configuration using functional options.
+type redeemcodeOption func(*RedeemCodeMutation)
-// newProxyMutation creates new mutation for the Proxy entity.
-func newProxyMutation(c config, op Op, opts ...proxyOption) *ProxyMutation {
- m := &ProxyMutation{
+// newRedeemCodeMutation creates new mutation for the RedeemCode entity.
+func newRedeemCodeMutation(c config, op Op, opts ...redeemcodeOption) *RedeemCodeMutation {
+ m := &RedeemCodeMutation{
config: c,
op: op,
- typ: TypeProxy,
+ typ: TypeRedeemCode,
clearedFields: make(map[string]struct{}),
}
for _, opt := range opts {
@@ -13792,20 +19205,20 @@ func newProxyMutation(c config, op Op, opts ...proxyOption) *ProxyMutation {
return m
}
-// withProxyID sets the ID field of the mutation.
-func withProxyID(id int64) proxyOption {
- return func(m *ProxyMutation) {
+// withRedeemCodeID sets the ID field of the mutation.
+func withRedeemCodeID(id int64) redeemcodeOption {
+ return func(m *RedeemCodeMutation) {
var (
err error
once sync.Once
- value *Proxy
+ value *RedeemCode
)
- m.oldValue = func(ctx context.Context) (*Proxy, error) {
+ m.oldValue = func(ctx context.Context) (*RedeemCode, error) {
once.Do(func() {
if m.done {
err = errors.New("querying old values post mutation is not allowed")
} else {
- value, err = m.Client().Proxy.Get(ctx, id)
+ value, err = m.Client().RedeemCode.Get(ctx, id)
}
})
return value, err
@@ -13814,10 +19227,10 @@ func withProxyID(id int64) proxyOption {
}
}
-// withProxy sets the old Proxy of the mutation.
-func withProxy(node *Proxy) proxyOption {
- return func(m *ProxyMutation) {
- m.oldValue = func(context.Context) (*Proxy, error) {
+// withRedeemCode sets the old RedeemCode of the mutation.
+func withRedeemCode(node *RedeemCode) redeemcodeOption {
+ return func(m *RedeemCodeMutation) {
+ m.oldValue = func(context.Context) (*RedeemCode, error) {
return node, nil
}
m.id = &node.ID
@@ -13826,7 +19239,7 @@ func withProxy(node *Proxy) proxyOption {
// Client returns a new `ent.Client` from the mutation. If the mutation was
// executed in a transaction (ent.Tx), a transactional client is returned.
-func (m ProxyMutation) Client() *Client {
+func (m RedeemCodeMutation) Client() *Client {
client := &Client{config: m.config}
client.init()
return client
@@ -13834,7 +19247,7 @@ func (m ProxyMutation) Client() *Client {
// Tx returns an `ent.Tx` for mutations that were executed in transactions;
// it returns an error otherwise.
-func (m ProxyMutation) Tx() (*Tx, error) {
+func (m RedeemCodeMutation) Tx() (*Tx, error) {
if _, ok := m.driver.(*txDriver); !ok {
return nil, errors.New("ent: mutation is not running in a transaction")
}
@@ -13845,7 +19258,7 @@ func (m ProxyMutation) Tx() (*Tx, error) {
// ID returns the ID value in the mutation. Note that the ID is only available
// if it was provided to the builder or after it was returned from the database.
-func (m *ProxyMutation) ID() (id int64, exists bool) {
+func (m *RedeemCodeMutation) ID() (id int64, exists bool) {
if m.id == nil {
return
}
@@ -13856,7 +19269,7 @@ func (m *ProxyMutation) ID() (id int64, exists bool) {
// That means, if the mutation is applied within a transaction with an isolation level such
// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated
// or updated by the mutation.
-func (m *ProxyMutation) IDs(ctx context.Context) ([]int64, error) {
+func (m *RedeemCodeMutation) IDs(ctx context.Context) ([]int64, error) {
switch {
case m.op.Is(OpUpdateOne | OpDeleteOne):
id, exists := m.ID()
@@ -13865,494 +19278,540 @@ func (m *ProxyMutation) IDs(ctx context.Context) ([]int64, error) {
}
fallthrough
case m.op.Is(OpUpdate | OpDelete):
- return m.Client().Proxy.Query().Where(m.predicates...).IDs(ctx)
+ return m.Client().RedeemCode.Query().Where(m.predicates...).IDs(ctx)
default:
return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op)
}
}
-// SetCreatedAt sets the "created_at" field.
-func (m *ProxyMutation) SetCreatedAt(t time.Time) {
- m.created_at = &t
+// SetCode sets the "code" field.
+func (m *RedeemCodeMutation) SetCode(s string) {
+ m.code = &s
}
-// CreatedAt returns the value of the "created_at" field in the mutation.
-func (m *ProxyMutation) CreatedAt() (r time.Time, exists bool) {
- v := m.created_at
+// Code returns the value of the "code" field in the mutation.
+func (m *RedeemCodeMutation) Code() (r string, exists bool) {
+ v := m.code
if v == nil {
return
}
return *v, true
}
-// OldCreatedAt returns the old "created_at" field's value of the Proxy entity.
-// If the Proxy object wasn't provided to the builder, the object is fetched from the database.
+// OldCode returns the old "code" field's value of the RedeemCode entity.
+// If the RedeemCode object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
-func (m *ProxyMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) {
+func (m *RedeemCodeMutation) OldCode(ctx context.Context) (v string, err error) {
if !m.op.Is(OpUpdateOne) {
- return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations")
+ return v, errors.New("OldCode is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
- return v, errors.New("OldCreatedAt requires an ID field in the mutation")
+ return v, errors.New("OldCode requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
- return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err)
+ return v, fmt.Errorf("querying old value for OldCode: %w", err)
}
- return oldValue.CreatedAt, nil
+ return oldValue.Code, nil
}
-// ResetCreatedAt resets all changes to the "created_at" field.
-func (m *ProxyMutation) ResetCreatedAt() {
- m.created_at = nil
+// ResetCode resets all changes to the "code" field.
+func (m *RedeemCodeMutation) ResetCode() {
+ m.code = nil
}
-// SetUpdatedAt sets the "updated_at" field.
-func (m *ProxyMutation) SetUpdatedAt(t time.Time) {
- m.updated_at = &t
+// SetType sets the "type" field.
+func (m *RedeemCodeMutation) SetType(s string) {
+ m._type = &s
}
-// UpdatedAt returns the value of the "updated_at" field in the mutation.
-func (m *ProxyMutation) UpdatedAt() (r time.Time, exists bool) {
- v := m.updated_at
+// GetType returns the value of the "type" field in the mutation.
+func (m *RedeemCodeMutation) GetType() (r string, exists bool) {
+ v := m._type
if v == nil {
return
}
return *v, true
}
-// OldUpdatedAt returns the old "updated_at" field's value of the Proxy entity.
-// If the Proxy object wasn't provided to the builder, the object is fetched from the database.
+// OldType returns the old "type" field's value of the RedeemCode entity.
+// If the RedeemCode object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
-func (m *ProxyMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) {
+func (m *RedeemCodeMutation) OldType(ctx context.Context) (v string, err error) {
if !m.op.Is(OpUpdateOne) {
- return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations")
+ return v, errors.New("OldType is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
- return v, errors.New("OldUpdatedAt requires an ID field in the mutation")
+ return v, errors.New("OldType requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
- return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err)
+ return v, fmt.Errorf("querying old value for OldType: %w", err)
}
- return oldValue.UpdatedAt, nil
+ return oldValue.Type, nil
}
-// ResetUpdatedAt resets all changes to the "updated_at" field.
-func (m *ProxyMutation) ResetUpdatedAt() {
- m.updated_at = nil
+// ResetType resets all changes to the "type" field.
+func (m *RedeemCodeMutation) ResetType() {
+ m._type = nil
}
-// SetDeletedAt sets the "deleted_at" field.
-func (m *ProxyMutation) SetDeletedAt(t time.Time) {
- m.deleted_at = &t
+// SetValue sets the "value" field.
+func (m *RedeemCodeMutation) SetValue(f float64) {
+ m.value = &f
+ m.addvalue = nil
}
-// DeletedAt returns the value of the "deleted_at" field in the mutation.
-func (m *ProxyMutation) DeletedAt() (r time.Time, exists bool) {
- v := m.deleted_at
+// Value returns the value of the "value" field in the mutation.
+func (m *RedeemCodeMutation) Value() (r float64, exists bool) {
+ v := m.value
if v == nil {
return
}
return *v, true
}
-// OldDeletedAt returns the old "deleted_at" field's value of the Proxy entity.
-// If the Proxy object wasn't provided to the builder, the object is fetched from the database.
+// OldValue returns the old "value" field's value of the RedeemCode entity.
+// If the RedeemCode object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
-func (m *ProxyMutation) OldDeletedAt(ctx context.Context) (v *time.Time, err error) {
+func (m *RedeemCodeMutation) OldValue(ctx context.Context) (v float64, err error) {
if !m.op.Is(OpUpdateOne) {
- return v, errors.New("OldDeletedAt is only allowed on UpdateOne operations")
+ return v, errors.New("OldValue is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
- return v, errors.New("OldDeletedAt requires an ID field in the mutation")
+ return v, errors.New("OldValue requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
- return v, fmt.Errorf("querying old value for OldDeletedAt: %w", err)
+ return v, fmt.Errorf("querying old value for OldValue: %w", err)
}
- return oldValue.DeletedAt, nil
+ return oldValue.Value, nil
}
-// ClearDeletedAt clears the value of the "deleted_at" field.
-func (m *ProxyMutation) ClearDeletedAt() {
- m.deleted_at = nil
- m.clearedFields[proxy.FieldDeletedAt] = struct{}{}
+// AddValue adds f to the "value" field.
+func (m *RedeemCodeMutation) AddValue(f float64) {
+ if m.addvalue != nil {
+ *m.addvalue += f
+ } else {
+ m.addvalue = &f
+ }
}
-// DeletedAtCleared returns if the "deleted_at" field was cleared in this mutation.
-func (m *ProxyMutation) DeletedAtCleared() bool {
- _, ok := m.clearedFields[proxy.FieldDeletedAt]
- return ok
+// AddedValue returns the value that was added to the "value" field in this mutation.
+func (m *RedeemCodeMutation) AddedValue() (r float64, exists bool) {
+ v := m.addvalue
+ if v == nil {
+ return
+ }
+ return *v, true
}
-// ResetDeletedAt resets all changes to the "deleted_at" field.
-func (m *ProxyMutation) ResetDeletedAt() {
- m.deleted_at = nil
- delete(m.clearedFields, proxy.FieldDeletedAt)
+// ResetValue resets all changes to the "value" field.
+func (m *RedeemCodeMutation) ResetValue() {
+ m.value = nil
+ m.addvalue = nil
}
-// SetName sets the "name" field.
-func (m *ProxyMutation) SetName(s string) {
- m.name = &s
+// SetStatus sets the "status" field.
+func (m *RedeemCodeMutation) SetStatus(s string) {
+ m.status = &s
}
-// Name returns the value of the "name" field in the mutation.
-func (m *ProxyMutation) Name() (r string, exists bool) {
- v := m.name
+// Status returns the value of the "status" field in the mutation.
+func (m *RedeemCodeMutation) Status() (r string, exists bool) {
+ v := m.status
if v == nil {
return
}
return *v, true
}
-// OldName returns the old "name" field's value of the Proxy entity.
-// If the Proxy object wasn't provided to the builder, the object is fetched from the database.
+// OldStatus returns the old "status" field's value of the RedeemCode entity.
+// If the RedeemCode object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
-func (m *ProxyMutation) OldName(ctx context.Context) (v string, err error) {
+func (m *RedeemCodeMutation) OldStatus(ctx context.Context) (v string, err error) {
if !m.op.Is(OpUpdateOne) {
- return v, errors.New("OldName is only allowed on UpdateOne operations")
+ return v, errors.New("OldStatus is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
- return v, errors.New("OldName requires an ID field in the mutation")
+ return v, errors.New("OldStatus requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
- return v, fmt.Errorf("querying old value for OldName: %w", err)
+ return v, fmt.Errorf("querying old value for OldStatus: %w", err)
}
- return oldValue.Name, nil
+ return oldValue.Status, nil
}
-// ResetName resets all changes to the "name" field.
-func (m *ProxyMutation) ResetName() {
- m.name = nil
+// ResetStatus resets all changes to the "status" field.
+func (m *RedeemCodeMutation) ResetStatus() {
+ m.status = nil
}
-// SetProtocol sets the "protocol" field.
-func (m *ProxyMutation) SetProtocol(s string) {
- m.protocol = &s
+// SetUsedBy sets the "used_by" field.
+func (m *RedeemCodeMutation) SetUsedBy(i int64) {
+ m.user = &i
}
-// Protocol returns the value of the "protocol" field in the mutation.
-func (m *ProxyMutation) Protocol() (r string, exists bool) {
- v := m.protocol
+// UsedBy returns the value of the "used_by" field in the mutation.
+func (m *RedeemCodeMutation) UsedBy() (r int64, exists bool) {
+ v := m.user
if v == nil {
return
}
return *v, true
}
-// OldProtocol returns the old "protocol" field's value of the Proxy entity.
-// If the Proxy object wasn't provided to the builder, the object is fetched from the database.
+// OldUsedBy returns the old "used_by" field's value of the RedeemCode entity.
+// If the RedeemCode object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
-func (m *ProxyMutation) OldProtocol(ctx context.Context) (v string, err error) {
+func (m *RedeemCodeMutation) OldUsedBy(ctx context.Context) (v *int64, err error) {
if !m.op.Is(OpUpdateOne) {
- return v, errors.New("OldProtocol is only allowed on UpdateOne operations")
+ return v, errors.New("OldUsedBy is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
- return v, errors.New("OldProtocol requires an ID field in the mutation")
+ return v, errors.New("OldUsedBy requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
- return v, fmt.Errorf("querying old value for OldProtocol: %w", err)
+ return v, fmt.Errorf("querying old value for OldUsedBy: %w", err)
}
- return oldValue.Protocol, nil
+ return oldValue.UsedBy, nil
}
-// ResetProtocol resets all changes to the "protocol" field.
-func (m *ProxyMutation) ResetProtocol() {
- m.protocol = nil
+// ClearUsedBy clears the value of the "used_by" field.
+func (m *RedeemCodeMutation) ClearUsedBy() {
+ m.user = nil
+ m.clearedFields[redeemcode.FieldUsedBy] = struct{}{}
}
-// SetHost sets the "host" field.
-func (m *ProxyMutation) SetHost(s string) {
- m.host = &s
+// UsedByCleared returns if the "used_by" field was cleared in this mutation.
+func (m *RedeemCodeMutation) UsedByCleared() bool {
+ _, ok := m.clearedFields[redeemcode.FieldUsedBy]
+ return ok
}
-// Host returns the value of the "host" field in the mutation.
-func (m *ProxyMutation) Host() (r string, exists bool) {
- v := m.host
+// ResetUsedBy resets all changes to the "used_by" field.
+func (m *RedeemCodeMutation) ResetUsedBy() {
+ m.user = nil
+ delete(m.clearedFields, redeemcode.FieldUsedBy)
+}
+
+// SetUsedAt sets the "used_at" field.
+func (m *RedeemCodeMutation) SetUsedAt(t time.Time) {
+ m.used_at = &t
+}
+
+// UsedAt returns the value of the "used_at" field in the mutation.
+func (m *RedeemCodeMutation) UsedAt() (r time.Time, exists bool) {
+ v := m.used_at
if v == nil {
return
}
return *v, true
}
-// OldHost returns the old "host" field's value of the Proxy entity.
-// If the Proxy object wasn't provided to the builder, the object is fetched from the database.
+// OldUsedAt returns the old "used_at" field's value of the RedeemCode entity.
+// If the RedeemCode object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
-func (m *ProxyMutation) OldHost(ctx context.Context) (v string, err error) {
+func (m *RedeemCodeMutation) OldUsedAt(ctx context.Context) (v *time.Time, err error) {
if !m.op.Is(OpUpdateOne) {
- return v, errors.New("OldHost is only allowed on UpdateOne operations")
+ return v, errors.New("OldUsedAt is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
- return v, errors.New("OldHost requires an ID field in the mutation")
+ return v, errors.New("OldUsedAt requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
- return v, fmt.Errorf("querying old value for OldHost: %w", err)
+ return v, fmt.Errorf("querying old value for OldUsedAt: %w", err)
}
- return oldValue.Host, nil
+ return oldValue.UsedAt, nil
}
-// ResetHost resets all changes to the "host" field.
-func (m *ProxyMutation) ResetHost() {
- m.host = nil
+// ClearUsedAt clears the value of the "used_at" field.
+func (m *RedeemCodeMutation) ClearUsedAt() {
+ m.used_at = nil
+ m.clearedFields[redeemcode.FieldUsedAt] = struct{}{}
}
-// SetPort sets the "port" field.
-func (m *ProxyMutation) SetPort(i int) {
- m.port = &i
- m.addport = nil
+// UsedAtCleared returns if the "used_at" field was cleared in this mutation.
+func (m *RedeemCodeMutation) UsedAtCleared() bool {
+ _, ok := m.clearedFields[redeemcode.FieldUsedAt]
+ return ok
}
-// Port returns the value of the "port" field in the mutation.
-func (m *ProxyMutation) Port() (r int, exists bool) {
- v := m.port
+// ResetUsedAt resets all changes to the "used_at" field.
+func (m *RedeemCodeMutation) ResetUsedAt() {
+ m.used_at = nil
+ delete(m.clearedFields, redeemcode.FieldUsedAt)
+}
+
+// SetNotes sets the "notes" field.
+func (m *RedeemCodeMutation) SetNotes(s string) {
+ m.notes = &s
+}
+
+// Notes returns the value of the "notes" field in the mutation.
+func (m *RedeemCodeMutation) Notes() (r string, exists bool) {
+ v := m.notes
if v == nil {
return
}
return *v, true
}
-// OldPort returns the old "port" field's value of the Proxy entity.
-// If the Proxy object wasn't provided to the builder, the object is fetched from the database.
+// OldNotes returns the old "notes" field's value of the RedeemCode entity.
+// If the RedeemCode object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
-func (m *ProxyMutation) OldPort(ctx context.Context) (v int, err error) {
+func (m *RedeemCodeMutation) OldNotes(ctx context.Context) (v *string, err error) {
if !m.op.Is(OpUpdateOne) {
- return v, errors.New("OldPort is only allowed on UpdateOne operations")
+ return v, errors.New("OldNotes is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
- return v, errors.New("OldPort requires an ID field in the mutation")
+ return v, errors.New("OldNotes requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
- return v, fmt.Errorf("querying old value for OldPort: %w", err)
+ return v, fmt.Errorf("querying old value for OldNotes: %w", err)
}
- return oldValue.Port, nil
+ return oldValue.Notes, nil
}
-// AddPort adds i to the "port" field.
-func (m *ProxyMutation) AddPort(i int) {
- if m.addport != nil {
- *m.addport += i
- } else {
- m.addport = &i
- }
+// ClearNotes clears the value of the "notes" field.
+func (m *RedeemCodeMutation) ClearNotes() {
+ m.notes = nil
+ m.clearedFields[redeemcode.FieldNotes] = struct{}{}
}
-// AddedPort returns the value that was added to the "port" field in this mutation.
-func (m *ProxyMutation) AddedPort() (r int, exists bool) {
- v := m.addport
- if v == nil {
- return
- }
- return *v, true
+// NotesCleared returns if the "notes" field was cleared in this mutation.
+func (m *RedeemCodeMutation) NotesCleared() bool {
+ _, ok := m.clearedFields[redeemcode.FieldNotes]
+ return ok
}
-// ResetPort resets all changes to the "port" field.
-func (m *ProxyMutation) ResetPort() {
- m.port = nil
- m.addport = nil
+// ResetNotes resets all changes to the "notes" field.
+func (m *RedeemCodeMutation) ResetNotes() {
+ m.notes = nil
+ delete(m.clearedFields, redeemcode.FieldNotes)
}
-// SetUsername sets the "username" field.
-func (m *ProxyMutation) SetUsername(s string) {
- m.username = &s
+// SetCreatedAt sets the "created_at" field.
+func (m *RedeemCodeMutation) SetCreatedAt(t time.Time) {
+ m.created_at = &t
}
-// Username returns the value of the "username" field in the mutation.
-func (m *ProxyMutation) Username() (r string, exists bool) {
- v := m.username
+// CreatedAt returns the value of the "created_at" field in the mutation.
+func (m *RedeemCodeMutation) CreatedAt() (r time.Time, exists bool) {
+ v := m.created_at
if v == nil {
return
}
return *v, true
}
-// OldUsername returns the old "username" field's value of the Proxy entity.
-// If the Proxy object wasn't provided to the builder, the object is fetched from the database.
+// OldCreatedAt returns the old "created_at" field's value of the RedeemCode entity.
+// If the RedeemCode object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
-func (m *ProxyMutation) OldUsername(ctx context.Context) (v *string, err error) {
+func (m *RedeemCodeMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) {
if !m.op.Is(OpUpdateOne) {
- return v, errors.New("OldUsername is only allowed on UpdateOne operations")
+ return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
- return v, errors.New("OldUsername requires an ID field in the mutation")
+ return v, errors.New("OldCreatedAt requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
- return v, fmt.Errorf("querying old value for OldUsername: %w", err)
+ return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err)
}
- return oldValue.Username, nil
-}
-
-// ClearUsername clears the value of the "username" field.
-func (m *ProxyMutation) ClearUsername() {
- m.username = nil
- m.clearedFields[proxy.FieldUsername] = struct{}{}
-}
-
-// UsernameCleared returns if the "username" field was cleared in this mutation.
-func (m *ProxyMutation) UsernameCleared() bool {
- _, ok := m.clearedFields[proxy.FieldUsername]
- return ok
+ return oldValue.CreatedAt, nil
}
-// ResetUsername resets all changes to the "username" field.
-func (m *ProxyMutation) ResetUsername() {
- m.username = nil
- delete(m.clearedFields, proxy.FieldUsername)
+// ResetCreatedAt resets all changes to the "created_at" field.
+func (m *RedeemCodeMutation) ResetCreatedAt() {
+ m.created_at = nil
}
-// SetPassword sets the "password" field.
-func (m *ProxyMutation) SetPassword(s string) {
- m.password = &s
+// SetGroupID sets the "group_id" field.
+func (m *RedeemCodeMutation) SetGroupID(i int64) {
+ m.group = &i
}
-// Password returns the value of the "password" field in the mutation.
-func (m *ProxyMutation) Password() (r string, exists bool) {
- v := m.password
+// GroupID returns the value of the "group_id" field in the mutation.
+func (m *RedeemCodeMutation) GroupID() (r int64, exists bool) {
+ v := m.group
if v == nil {
return
}
return *v, true
}
-// OldPassword returns the old "password" field's value of the Proxy entity.
-// If the Proxy object wasn't provided to the builder, the object is fetched from the database.
+// OldGroupID returns the old "group_id" field's value of the RedeemCode entity.
+// If the RedeemCode object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
-func (m *ProxyMutation) OldPassword(ctx context.Context) (v *string, err error) {
+func (m *RedeemCodeMutation) OldGroupID(ctx context.Context) (v *int64, err error) {
if !m.op.Is(OpUpdateOne) {
- return v, errors.New("OldPassword is only allowed on UpdateOne operations")
+ return v, errors.New("OldGroupID is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
- return v, errors.New("OldPassword requires an ID field in the mutation")
+ return v, errors.New("OldGroupID requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
- return v, fmt.Errorf("querying old value for OldPassword: %w", err)
+ return v, fmt.Errorf("querying old value for OldGroupID: %w", err)
}
- return oldValue.Password, nil
+ return oldValue.GroupID, nil
}
-// ClearPassword clears the value of the "password" field.
-func (m *ProxyMutation) ClearPassword() {
- m.password = nil
- m.clearedFields[proxy.FieldPassword] = struct{}{}
+// ClearGroupID clears the value of the "group_id" field.
+func (m *RedeemCodeMutation) ClearGroupID() {
+ m.group = nil
+ m.clearedFields[redeemcode.FieldGroupID] = struct{}{}
}
-// PasswordCleared returns if the "password" field was cleared in this mutation.
-func (m *ProxyMutation) PasswordCleared() bool {
- _, ok := m.clearedFields[proxy.FieldPassword]
+// GroupIDCleared returns if the "group_id" field was cleared in this mutation.
+func (m *RedeemCodeMutation) GroupIDCleared() bool {
+ _, ok := m.clearedFields[redeemcode.FieldGroupID]
return ok
}
-// ResetPassword resets all changes to the "password" field.
-func (m *ProxyMutation) ResetPassword() {
- m.password = nil
- delete(m.clearedFields, proxy.FieldPassword)
+// ResetGroupID resets all changes to the "group_id" field.
+func (m *RedeemCodeMutation) ResetGroupID() {
+ m.group = nil
+ delete(m.clearedFields, redeemcode.FieldGroupID)
}
-// SetStatus sets the "status" field.
-func (m *ProxyMutation) SetStatus(s string) {
- m.status = &s
+// SetValidityDays sets the "validity_days" field.
+func (m *RedeemCodeMutation) SetValidityDays(i int) {
+ m.validity_days = &i
+ m.addvalidity_days = nil
}
-// Status returns the value of the "status" field in the mutation.
-func (m *ProxyMutation) Status() (r string, exists bool) {
- v := m.status
+// ValidityDays returns the value of the "validity_days" field in the mutation.
+func (m *RedeemCodeMutation) ValidityDays() (r int, exists bool) {
+ v := m.validity_days
if v == nil {
return
}
return *v, true
}
-// OldStatus returns the old "status" field's value of the Proxy entity.
-// If the Proxy object wasn't provided to the builder, the object is fetched from the database.
+// OldValidityDays returns the old "validity_days" field's value of the RedeemCode entity.
+// If the RedeemCode object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
-func (m *ProxyMutation) OldStatus(ctx context.Context) (v string, err error) {
+func (m *RedeemCodeMutation) OldValidityDays(ctx context.Context) (v int, err error) {
if !m.op.Is(OpUpdateOne) {
- return v, errors.New("OldStatus is only allowed on UpdateOne operations")
+ return v, errors.New("OldValidityDays is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
- return v, errors.New("OldStatus requires an ID field in the mutation")
+ return v, errors.New("OldValidityDays requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
- return v, fmt.Errorf("querying old value for OldStatus: %w", err)
+ return v, fmt.Errorf("querying old value for OldValidityDays: %w", err)
}
- return oldValue.Status, nil
+ return oldValue.ValidityDays, nil
}
-// ResetStatus resets all changes to the "status" field.
-func (m *ProxyMutation) ResetStatus() {
- m.status = nil
+// AddValidityDays adds i to the "validity_days" field.
+func (m *RedeemCodeMutation) AddValidityDays(i int) {
+ if m.addvalidity_days != nil {
+ *m.addvalidity_days += i
+ } else {
+ m.addvalidity_days = &i
+ }
}
-// AddAccountIDs adds the "accounts" edge to the Account entity by ids.
-func (m *ProxyMutation) AddAccountIDs(ids ...int64) {
- if m.accounts == nil {
- m.accounts = make(map[int64]struct{})
- }
- for i := range ids {
- m.accounts[ids[i]] = struct{}{}
+// AddedValidityDays returns the value that was added to the "validity_days" field in this mutation.
+func (m *RedeemCodeMutation) AddedValidityDays() (r int, exists bool) {
+ v := m.addvalidity_days
+ if v == nil {
+ return
}
+ return *v, true
}
-// ClearAccounts clears the "accounts" edge to the Account entity.
-func (m *ProxyMutation) ClearAccounts() {
- m.clearedaccounts = true
+// ResetValidityDays resets all changes to the "validity_days" field.
+func (m *RedeemCodeMutation) ResetValidityDays() {
+ m.validity_days = nil
+ m.addvalidity_days = nil
}
-// AccountsCleared reports if the "accounts" edge to the Account entity was cleared.
-func (m *ProxyMutation) AccountsCleared() bool {
- return m.clearedaccounts
+// SetUserID sets the "user" edge to the User entity by id.
+func (m *RedeemCodeMutation) SetUserID(id int64) {
+ m.user = &id
}
-// RemoveAccountIDs removes the "accounts" edge to the Account entity by IDs.
-func (m *ProxyMutation) RemoveAccountIDs(ids ...int64) {
- if m.removedaccounts == nil {
- m.removedaccounts = make(map[int64]struct{})
- }
- for i := range ids {
- delete(m.accounts, ids[i])
- m.removedaccounts[ids[i]] = struct{}{}
+// ClearUser clears the "user" edge to the User entity.
+func (m *RedeemCodeMutation) ClearUser() {
+ m.cleareduser = true
+ m.clearedFields[redeemcode.FieldUsedBy] = struct{}{}
+}
+
+// UserCleared reports if the "user" edge to the User entity was cleared.
+func (m *RedeemCodeMutation) UserCleared() bool {
+ return m.UsedByCleared() || m.cleareduser
+}
+
+// UserID returns the "user" edge ID in the mutation.
+func (m *RedeemCodeMutation) UserID() (id int64, exists bool) {
+ if m.user != nil {
+ return *m.user, true
}
+ return
}
-// RemovedAccounts returns the removed IDs of the "accounts" edge to the Account entity.
-func (m *ProxyMutation) RemovedAccountsIDs() (ids []int64) {
- for id := range m.removedaccounts {
- ids = append(ids, id)
+// UserIDs returns the "user" edge IDs in the mutation.
+// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use
+// UserID instead. It exists only for internal usage by the builders.
+func (m *RedeemCodeMutation) UserIDs() (ids []int64) {
+ if id := m.user; id != nil {
+ ids = append(ids, *id)
}
return
}
-// AccountsIDs returns the "accounts" edge IDs in the mutation.
-func (m *ProxyMutation) AccountsIDs() (ids []int64) {
- for id := range m.accounts {
- ids = append(ids, id)
+// ResetUser resets all changes to the "user" edge.
+func (m *RedeemCodeMutation) ResetUser() {
+ m.user = nil
+ m.cleareduser = false
+}
+
+// ClearGroup clears the "group" edge to the Group entity.
+func (m *RedeemCodeMutation) ClearGroup() {
+ m.clearedgroup = true
+ m.clearedFields[redeemcode.FieldGroupID] = struct{}{}
+}
+
+// GroupCleared reports if the "group" edge to the Group entity was cleared.
+func (m *RedeemCodeMutation) GroupCleared() bool {
+ return m.GroupIDCleared() || m.clearedgroup
+}
+
+// GroupIDs returns the "group" edge IDs in the mutation.
+// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use
+// GroupID instead. It exists only for internal usage by the builders.
+func (m *RedeemCodeMutation) GroupIDs() (ids []int64) {
+ if id := m.group; id != nil {
+ ids = append(ids, *id)
}
return
}
-// ResetAccounts resets all changes to the "accounts" edge.
-func (m *ProxyMutation) ResetAccounts() {
- m.accounts = nil
- m.clearedaccounts = false
- m.removedaccounts = nil
+// ResetGroup resets all changes to the "group" edge.
+func (m *RedeemCodeMutation) ResetGroup() {
+ m.group = nil
+ m.clearedgroup = false
}
-// Where appends a list predicates to the ProxyMutation builder.
-func (m *ProxyMutation) Where(ps ...predicate.Proxy) {
+// Where appends a list predicates to the RedeemCodeMutation builder.
+func (m *RedeemCodeMutation) Where(ps ...predicate.RedeemCode) {
m.predicates = append(m.predicates, ps...)
}
-// WhereP appends storage-level predicates to the ProxyMutation builder. Using this method,
+// WhereP appends storage-level predicates to the RedeemCodeMutation builder. Using this method,
// users can use type-assertion to append predicates that do not depend on any generated package.
-func (m *ProxyMutation) WhereP(ps ...func(*sql.Selector)) {
- p := make([]predicate.Proxy, len(ps))
+func (m *RedeemCodeMutation) WhereP(ps ...func(*sql.Selector)) {
+ p := make([]predicate.RedeemCode, len(ps))
for i := range ps {
p[i] = ps[i]
}
@@ -14360,54 +19819,54 @@ func (m *ProxyMutation) WhereP(ps ...func(*sql.Selector)) {
}
// Op returns the operation name.
-func (m *ProxyMutation) Op() Op {
+func (m *RedeemCodeMutation) Op() Op {
return m.op
}
// SetOp allows setting the mutation operation.
-func (m *ProxyMutation) SetOp(op Op) {
+func (m *RedeemCodeMutation) SetOp(op Op) {
m.op = op
}
-// Type returns the node type of this mutation (Proxy).
-func (m *ProxyMutation) Type() string {
+// Type returns the node type of this mutation (RedeemCode).
+func (m *RedeemCodeMutation) Type() string {
return m.typ
}
// Fields returns all fields that were changed during this mutation. Note that in
// order to get all numeric fields that were incremented/decremented, call
// AddedFields().
-func (m *ProxyMutation) Fields() []string {
+func (m *RedeemCodeMutation) Fields() []string {
fields := make([]string, 0, 10)
- if m.created_at != nil {
- fields = append(fields, proxy.FieldCreatedAt)
+ if m.code != nil {
+ fields = append(fields, redeemcode.FieldCode)
}
- if m.updated_at != nil {
- fields = append(fields, proxy.FieldUpdatedAt)
+ if m._type != nil {
+ fields = append(fields, redeemcode.FieldType)
}
- if m.deleted_at != nil {
- fields = append(fields, proxy.FieldDeletedAt)
+ if m.value != nil {
+ fields = append(fields, redeemcode.FieldValue)
}
- if m.name != nil {
- fields = append(fields, proxy.FieldName)
+ if m.status != nil {
+ fields = append(fields, redeemcode.FieldStatus)
}
- if m.protocol != nil {
- fields = append(fields, proxy.FieldProtocol)
+ if m.user != nil {
+ fields = append(fields, redeemcode.FieldUsedBy)
}
- if m.host != nil {
- fields = append(fields, proxy.FieldHost)
+ if m.used_at != nil {
+ fields = append(fields, redeemcode.FieldUsedAt)
}
- if m.port != nil {
- fields = append(fields, proxy.FieldPort)
+ if m.notes != nil {
+ fields = append(fields, redeemcode.FieldNotes)
}
- if m.username != nil {
- fields = append(fields, proxy.FieldUsername)
+ if m.created_at != nil {
+ fields = append(fields, redeemcode.FieldCreatedAt)
}
- if m.password != nil {
- fields = append(fields, proxy.FieldPassword)
+ if m.group != nil {
+ fields = append(fields, redeemcode.FieldGroupID)
}
- if m.status != nil {
- fields = append(fields, proxy.FieldStatus)
+ if m.validity_days != nil {
+ fields = append(fields, redeemcode.FieldValidityDays)
}
return fields
}
@@ -14415,28 +19874,28 @@ func (m *ProxyMutation) Fields() []string {
// Field returns the value of a field with the given name. The second boolean
// return value indicates that this field was not set, or was not defined in the
// schema.
-func (m *ProxyMutation) Field(name string) (ent.Value, bool) {
+func (m *RedeemCodeMutation) Field(name string) (ent.Value, bool) {
switch name {
- case proxy.FieldCreatedAt:
- return m.CreatedAt()
- case proxy.FieldUpdatedAt:
- return m.UpdatedAt()
- case proxy.FieldDeletedAt:
- return m.DeletedAt()
- case proxy.FieldName:
- return m.Name()
- case proxy.FieldProtocol:
- return m.Protocol()
- case proxy.FieldHost:
- return m.Host()
- case proxy.FieldPort:
- return m.Port()
- case proxy.FieldUsername:
- return m.Username()
- case proxy.FieldPassword:
- return m.Password()
- case proxy.FieldStatus:
+ case redeemcode.FieldCode:
+ return m.Code()
+ case redeemcode.FieldType:
+ return m.GetType()
+ case redeemcode.FieldValue:
+ return m.Value()
+ case redeemcode.FieldStatus:
return m.Status()
+ case redeemcode.FieldUsedBy:
+ return m.UsedBy()
+ case redeemcode.FieldUsedAt:
+ return m.UsedAt()
+ case redeemcode.FieldNotes:
+ return m.Notes()
+ case redeemcode.FieldCreatedAt:
+ return m.CreatedAt()
+ case redeemcode.FieldGroupID:
+ return m.GroupID()
+ case redeemcode.FieldValidityDays:
+ return m.ValidityDays()
}
return nil, false
}
@@ -14444,117 +19903,120 @@ func (m *ProxyMutation) Field(name string) (ent.Value, bool) {
// OldField returns the old value of the field from the database. An error is
// returned if the mutation operation is not UpdateOne, or the query to the
// database failed.
-func (m *ProxyMutation) OldField(ctx context.Context, name string) (ent.Value, error) {
+func (m *RedeemCodeMutation) OldField(ctx context.Context, name string) (ent.Value, error) {
switch name {
- case proxy.FieldCreatedAt:
- return m.OldCreatedAt(ctx)
- case proxy.FieldUpdatedAt:
- return m.OldUpdatedAt(ctx)
- case proxy.FieldDeletedAt:
- return m.OldDeletedAt(ctx)
- case proxy.FieldName:
- return m.OldName(ctx)
- case proxy.FieldProtocol:
- return m.OldProtocol(ctx)
- case proxy.FieldHost:
- return m.OldHost(ctx)
- case proxy.FieldPort:
- return m.OldPort(ctx)
- case proxy.FieldUsername:
- return m.OldUsername(ctx)
- case proxy.FieldPassword:
- return m.OldPassword(ctx)
- case proxy.FieldStatus:
+ case redeemcode.FieldCode:
+ return m.OldCode(ctx)
+ case redeemcode.FieldType:
+ return m.OldType(ctx)
+ case redeemcode.FieldValue:
+ return m.OldValue(ctx)
+ case redeemcode.FieldStatus:
return m.OldStatus(ctx)
+ case redeemcode.FieldUsedBy:
+ return m.OldUsedBy(ctx)
+ case redeemcode.FieldUsedAt:
+ return m.OldUsedAt(ctx)
+ case redeemcode.FieldNotes:
+ return m.OldNotes(ctx)
+ case redeemcode.FieldCreatedAt:
+ return m.OldCreatedAt(ctx)
+ case redeemcode.FieldGroupID:
+ return m.OldGroupID(ctx)
+ case redeemcode.FieldValidityDays:
+ return m.OldValidityDays(ctx)
}
- return nil, fmt.Errorf("unknown Proxy field %s", name)
+ return nil, fmt.Errorf("unknown RedeemCode field %s", name)
}
// SetField sets the value of a field with the given name. It returns an error if
// the field is not defined in the schema, or if the type mismatched the field
// type.
-func (m *ProxyMutation) SetField(name string, value ent.Value) error {
+func (m *RedeemCodeMutation) SetField(name string, value ent.Value) error {
switch name {
- case proxy.FieldCreatedAt:
- v, ok := value.(time.Time)
+ case redeemcode.FieldCode:
+ v, ok := value.(string)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
- m.SetCreatedAt(v)
+ m.SetCode(v)
return nil
- case proxy.FieldUpdatedAt:
- v, ok := value.(time.Time)
+ case redeemcode.FieldType:
+ v, ok := value.(string)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
- m.SetUpdatedAt(v)
+ m.SetType(v)
return nil
- case proxy.FieldDeletedAt:
- v, ok := value.(time.Time)
+ case redeemcode.FieldValue:
+ v, ok := value.(float64)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
- m.SetDeletedAt(v)
+ m.SetValue(v)
return nil
- case proxy.FieldName:
+ case redeemcode.FieldStatus:
v, ok := value.(string)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
- m.SetName(v)
+ m.SetStatus(v)
return nil
- case proxy.FieldProtocol:
- v, ok := value.(string)
+ case redeemcode.FieldUsedBy:
+ v, ok := value.(int64)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
- m.SetProtocol(v)
+ m.SetUsedBy(v)
return nil
- case proxy.FieldHost:
- v, ok := value.(string)
+ case redeemcode.FieldUsedAt:
+ v, ok := value.(time.Time)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
- m.SetHost(v)
+ m.SetUsedAt(v)
return nil
- case proxy.FieldPort:
- v, ok := value.(int)
+ case redeemcode.FieldNotes:
+ v, ok := value.(string)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
- m.SetPort(v)
+ m.SetNotes(v)
return nil
- case proxy.FieldUsername:
- v, ok := value.(string)
+ case redeemcode.FieldCreatedAt:
+ v, ok := value.(time.Time)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
- m.SetUsername(v)
+ m.SetCreatedAt(v)
return nil
- case proxy.FieldPassword:
- v, ok := value.(string)
+ case redeemcode.FieldGroupID:
+ v, ok := value.(int64)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
- m.SetPassword(v)
+ m.SetGroupID(v)
return nil
- case proxy.FieldStatus:
- v, ok := value.(string)
+ case redeemcode.FieldValidityDays:
+ v, ok := value.(int)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
- m.SetStatus(v)
+ m.SetValidityDays(v)
return nil
}
- return fmt.Errorf("unknown Proxy field %s", name)
+ return fmt.Errorf("unknown RedeemCode field %s", name)
}
// AddedFields returns all numeric fields that were incremented/decremented during
// this mutation.
-func (m *ProxyMutation) AddedFields() []string {
+func (m *RedeemCodeMutation) AddedFields() []string {
var fields []string
- if m.addport != nil {
- fields = append(fields, proxy.FieldPort)
+ if m.addvalue != nil {
+ fields = append(fields, redeemcode.FieldValue)
+ }
+ if m.addvalidity_days != nil {
+ fields = append(fields, redeemcode.FieldValidityDays)
}
return fields
}
@@ -14562,10 +20024,12 @@ func (m *ProxyMutation) AddedFields() []string {
// AddedField returns the numeric value that was incremented/decremented on a field
// with the given name. The second boolean return value indicates that this field
// was not set, or was not defined in the schema.
-func (m *ProxyMutation) AddedField(name string) (ent.Value, bool) {
+func (m *RedeemCodeMutation) AddedField(name string) (ent.Value, bool) {
switch name {
- case proxy.FieldPort:
- return m.AddedPort()
+ case redeemcode.FieldValue:
+ return m.AddedValue()
+ case redeemcode.FieldValidityDays:
+ return m.AddedValidityDays()
}
return nil, false
}
@@ -14573,218 +20037,229 @@ func (m *ProxyMutation) AddedField(name string) (ent.Value, bool) {
// AddField adds the value to the field with the given name. It returns an error if
// the field is not defined in the schema, or if the type mismatched the field
// type.
-func (m *ProxyMutation) AddField(name string, value ent.Value) error {
+func (m *RedeemCodeMutation) AddField(name string, value ent.Value) error {
switch name {
- case proxy.FieldPort:
+ case redeemcode.FieldValue:
+ v, ok := value.(float64)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.AddValue(v)
+ return nil
+ case redeemcode.FieldValidityDays:
v, ok := value.(int)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
- m.AddPort(v)
+ m.AddValidityDays(v)
return nil
}
- return fmt.Errorf("unknown Proxy numeric field %s", name)
+ return fmt.Errorf("unknown RedeemCode numeric field %s", name)
}
// ClearedFields returns all nullable fields that were cleared during this
// mutation.
-func (m *ProxyMutation) ClearedFields() []string {
+func (m *RedeemCodeMutation) ClearedFields() []string {
var fields []string
- if m.FieldCleared(proxy.FieldDeletedAt) {
- fields = append(fields, proxy.FieldDeletedAt)
+ if m.FieldCleared(redeemcode.FieldUsedBy) {
+ fields = append(fields, redeemcode.FieldUsedBy)
}
- if m.FieldCleared(proxy.FieldUsername) {
- fields = append(fields, proxy.FieldUsername)
+ if m.FieldCleared(redeemcode.FieldUsedAt) {
+ fields = append(fields, redeemcode.FieldUsedAt)
}
- if m.FieldCleared(proxy.FieldPassword) {
- fields = append(fields, proxy.FieldPassword)
+ if m.FieldCleared(redeemcode.FieldNotes) {
+ fields = append(fields, redeemcode.FieldNotes)
+ }
+ if m.FieldCleared(redeemcode.FieldGroupID) {
+ fields = append(fields, redeemcode.FieldGroupID)
}
return fields
}
// FieldCleared returns a boolean indicating if a field with the given name was
// cleared in this mutation.
-func (m *ProxyMutation) FieldCleared(name string) bool {
+func (m *RedeemCodeMutation) FieldCleared(name string) bool {
_, ok := m.clearedFields[name]
return ok
}
// ClearField clears the value of the field with the given name. It returns an
// error if the field is not defined in the schema.
-func (m *ProxyMutation) ClearField(name string) error {
+func (m *RedeemCodeMutation) ClearField(name string) error {
switch name {
- case proxy.FieldDeletedAt:
- m.ClearDeletedAt()
+ case redeemcode.FieldUsedBy:
+ m.ClearUsedBy()
return nil
- case proxy.FieldUsername:
- m.ClearUsername()
+ case redeemcode.FieldUsedAt:
+ m.ClearUsedAt()
return nil
- case proxy.FieldPassword:
- m.ClearPassword()
+ case redeemcode.FieldNotes:
+ m.ClearNotes()
+ return nil
+ case redeemcode.FieldGroupID:
+ m.ClearGroupID()
return nil
}
- return fmt.Errorf("unknown Proxy nullable field %s", name)
+ return fmt.Errorf("unknown RedeemCode nullable field %s", name)
}
// ResetField resets all changes in the mutation for the field with the given name.
// It returns an error if the field is not defined in the schema.
-func (m *ProxyMutation) ResetField(name string) error {
+func (m *RedeemCodeMutation) ResetField(name string) error {
switch name {
- case proxy.FieldCreatedAt:
- m.ResetCreatedAt()
+ case redeemcode.FieldCode:
+ m.ResetCode()
return nil
- case proxy.FieldUpdatedAt:
- m.ResetUpdatedAt()
+ case redeemcode.FieldType:
+ m.ResetType()
return nil
- case proxy.FieldDeletedAt:
- m.ResetDeletedAt()
+ case redeemcode.FieldValue:
+ m.ResetValue()
return nil
- case proxy.FieldName:
- m.ResetName()
+ case redeemcode.FieldStatus:
+ m.ResetStatus()
return nil
- case proxy.FieldProtocol:
- m.ResetProtocol()
+ case redeemcode.FieldUsedBy:
+ m.ResetUsedBy()
return nil
- case proxy.FieldHost:
- m.ResetHost()
+ case redeemcode.FieldUsedAt:
+ m.ResetUsedAt()
return nil
- case proxy.FieldPort:
- m.ResetPort()
+ case redeemcode.FieldNotes:
+ m.ResetNotes()
return nil
- case proxy.FieldUsername:
- m.ResetUsername()
+ case redeemcode.FieldCreatedAt:
+ m.ResetCreatedAt()
return nil
- case proxy.FieldPassword:
- m.ResetPassword()
+ case redeemcode.FieldGroupID:
+ m.ResetGroupID()
return nil
- case proxy.FieldStatus:
- m.ResetStatus()
+ case redeemcode.FieldValidityDays:
+ m.ResetValidityDays()
return nil
}
- return fmt.Errorf("unknown Proxy field %s", name)
+ return fmt.Errorf("unknown RedeemCode field %s", name)
}
// AddedEdges returns all edge names that were set/added in this mutation.
-func (m *ProxyMutation) AddedEdges() []string {
- edges := make([]string, 0, 1)
- if m.accounts != nil {
- edges = append(edges, proxy.EdgeAccounts)
+func (m *RedeemCodeMutation) AddedEdges() []string {
+ edges := make([]string, 0, 2)
+ if m.user != nil {
+ edges = append(edges, redeemcode.EdgeUser)
+ }
+ if m.group != nil {
+ edges = append(edges, redeemcode.EdgeGroup)
}
return edges
}
// AddedIDs returns all IDs (to other nodes) that were added for the given edge
// name in this mutation.
-func (m *ProxyMutation) AddedIDs(name string) []ent.Value {
+func (m *RedeemCodeMutation) AddedIDs(name string) []ent.Value {
switch name {
- case proxy.EdgeAccounts:
- ids := make([]ent.Value, 0, len(m.accounts))
- for id := range m.accounts {
- ids = append(ids, id)
+ case redeemcode.EdgeUser:
+ if id := m.user; id != nil {
+ return []ent.Value{*id}
+ }
+ case redeemcode.EdgeGroup:
+ if id := m.group; id != nil {
+ return []ent.Value{*id}
}
- return ids
}
return nil
}
// RemovedEdges returns all edge names that were removed in this mutation.
-func (m *ProxyMutation) RemovedEdges() []string {
- edges := make([]string, 0, 1)
- if m.removedaccounts != nil {
- edges = append(edges, proxy.EdgeAccounts)
- }
+func (m *RedeemCodeMutation) RemovedEdges() []string {
+ edges := make([]string, 0, 2)
return edges
}
// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with
// the given name in this mutation.
-func (m *ProxyMutation) RemovedIDs(name string) []ent.Value {
- switch name {
- case proxy.EdgeAccounts:
- ids := make([]ent.Value, 0, len(m.removedaccounts))
- for id := range m.removedaccounts {
- ids = append(ids, id)
- }
- return ids
- }
+func (m *RedeemCodeMutation) RemovedIDs(name string) []ent.Value {
return nil
}
// ClearedEdges returns all edge names that were cleared in this mutation.
-func (m *ProxyMutation) ClearedEdges() []string {
- edges := make([]string, 0, 1)
- if m.clearedaccounts {
- edges = append(edges, proxy.EdgeAccounts)
+func (m *RedeemCodeMutation) ClearedEdges() []string {
+ edges := make([]string, 0, 2)
+ if m.cleareduser {
+ edges = append(edges, redeemcode.EdgeUser)
+ }
+ if m.clearedgroup {
+ edges = append(edges, redeemcode.EdgeGroup)
}
return edges
}
// EdgeCleared returns a boolean which indicates if the edge with the given name
// was cleared in this mutation.
-func (m *ProxyMutation) EdgeCleared(name string) bool {
+func (m *RedeemCodeMutation) EdgeCleared(name string) bool {
switch name {
- case proxy.EdgeAccounts:
- return m.clearedaccounts
+ case redeemcode.EdgeUser:
+ return m.cleareduser
+ case redeemcode.EdgeGroup:
+ return m.clearedgroup
}
return false
}
// ClearEdge clears the value of the edge with the given name. It returns an error
// if that edge is not defined in the schema.
-func (m *ProxyMutation) ClearEdge(name string) error {
+func (m *RedeemCodeMutation) ClearEdge(name string) error {
switch name {
+ case redeemcode.EdgeUser:
+ m.ClearUser()
+ return nil
+ case redeemcode.EdgeGroup:
+ m.ClearGroup()
+ return nil
}
- return fmt.Errorf("unknown Proxy unique edge %s", name)
+ return fmt.Errorf("unknown RedeemCode unique edge %s", name)
}
// ResetEdge resets all changes to the edge with the given name in this mutation.
// It returns an error if the edge is not defined in the schema.
-func (m *ProxyMutation) ResetEdge(name string) error {
+func (m *RedeemCodeMutation) ResetEdge(name string) error {
switch name {
- case proxy.EdgeAccounts:
- m.ResetAccounts()
+ case redeemcode.EdgeUser:
+ m.ResetUser()
+ return nil
+ case redeemcode.EdgeGroup:
+ m.ResetGroup()
return nil
}
- return fmt.Errorf("unknown Proxy edge %s", name)
+ return fmt.Errorf("unknown RedeemCode edge %s", name)
}
-// RedeemCodeMutation represents an operation that mutates the RedeemCode nodes in the graph.
-type RedeemCodeMutation struct {
+// SecuritySecretMutation represents an operation that mutates the SecuritySecret nodes in the graph.
+type SecuritySecretMutation struct {
config
- op Op
- typ string
- id *int64
- code *string
- _type *string
- value *float64
- addvalue *float64
- status *string
- used_at *time.Time
- notes *string
- created_at *time.Time
- validity_days *int
- addvalidity_days *int
- clearedFields map[string]struct{}
- user *int64
- cleareduser bool
- group *int64
- clearedgroup bool
- done bool
- oldValue func(context.Context) (*RedeemCode, error)
- predicates []predicate.RedeemCode
+ op Op
+ typ string
+ id *int64
+ created_at *time.Time
+ updated_at *time.Time
+ key *string
+ value *string
+ clearedFields map[string]struct{}
+ done bool
+ oldValue func(context.Context) (*SecuritySecret, error)
+ predicates []predicate.SecuritySecret
}
-var _ ent.Mutation = (*RedeemCodeMutation)(nil)
+var _ ent.Mutation = (*SecuritySecretMutation)(nil)
-// redeemcodeOption allows management of the mutation configuration using functional options.
-type redeemcodeOption func(*RedeemCodeMutation)
+// securitysecretOption allows management of the mutation configuration using functional options.
+type securitysecretOption func(*SecuritySecretMutation)
-// newRedeemCodeMutation creates new mutation for the RedeemCode entity.
-func newRedeemCodeMutation(c config, op Op, opts ...redeemcodeOption) *RedeemCodeMutation {
- m := &RedeemCodeMutation{
+// newSecuritySecretMutation creates new mutation for the SecuritySecret entity.
+func newSecuritySecretMutation(c config, op Op, opts ...securitysecretOption) *SecuritySecretMutation {
+ m := &SecuritySecretMutation{
config: c,
op: op,
- typ: TypeRedeemCode,
+ typ: TypeSecuritySecret,
clearedFields: make(map[string]struct{}),
}
for _, opt := range opts {
@@ -14793,20 +20268,20 @@ func newRedeemCodeMutation(c config, op Op, opts ...redeemcodeOption) *RedeemCod
return m
}
-// withRedeemCodeID sets the ID field of the mutation.
-func withRedeemCodeID(id int64) redeemcodeOption {
- return func(m *RedeemCodeMutation) {
+// withSecuritySecretID sets the ID field of the mutation.
+func withSecuritySecretID(id int64) securitysecretOption {
+ return func(m *SecuritySecretMutation) {
var (
err error
once sync.Once
- value *RedeemCode
+ value *SecuritySecret
)
- m.oldValue = func(ctx context.Context) (*RedeemCode, error) {
+ m.oldValue = func(ctx context.Context) (*SecuritySecret, error) {
once.Do(func() {
if m.done {
err = errors.New("querying old values post mutation is not allowed")
} else {
- value, err = m.Client().RedeemCode.Get(ctx, id)
+ value, err = m.Client().SecuritySecret.Get(ctx, id)
}
})
return value, err
@@ -14815,10 +20290,10 @@ func withRedeemCodeID(id int64) redeemcodeOption {
}
}
-// withRedeemCode sets the old RedeemCode of the mutation.
-func withRedeemCode(node *RedeemCode) redeemcodeOption {
- return func(m *RedeemCodeMutation) {
- m.oldValue = func(context.Context) (*RedeemCode, error) {
+// withSecuritySecret sets the old SecuritySecret of the mutation.
+func withSecuritySecret(node *SecuritySecret) securitysecretOption {
+ return func(m *SecuritySecretMutation) {
+ m.oldValue = func(context.Context) (*SecuritySecret, error) {
return node, nil
}
m.id = &node.ID
@@ -14827,7 +20302,7 @@ func withRedeemCode(node *RedeemCode) redeemcodeOption {
// Client returns a new `ent.Client` from the mutation. If the mutation was
// executed in a transaction (ent.Tx), a transactional client is returned.
-func (m RedeemCodeMutation) Client() *Client {
+func (m SecuritySecretMutation) Client() *Client {
client := &Client{config: m.config}
client.init()
return client
@@ -14835,7 +20310,7 @@ func (m RedeemCodeMutation) Client() *Client {
// Tx returns an `ent.Tx` for mutations that were executed in transactions;
// it returns an error otherwise.
-func (m RedeemCodeMutation) Tx() (*Tx, error) {
+func (m SecuritySecretMutation) Tx() (*Tx, error) {
if _, ok := m.driver.(*txDriver); !ok {
return nil, errors.New("ent: mutation is not running in a transaction")
}
@@ -14846,7 +20321,7 @@ func (m RedeemCodeMutation) Tx() (*Tx, error) {
// ID returns the ID value in the mutation. Note that the ID is only available
// if it was provided to the builder or after it was returned from the database.
-func (m *RedeemCodeMutation) ID() (id int64, exists bool) {
+func (m *SecuritySecretMutation) ID() (id int64, exists bool) {
if m.id == nil {
return
}
@@ -14857,7 +20332,7 @@ func (m *RedeemCodeMutation) ID() (id int64, exists bool) {
// That means, if the mutation is applied within a transaction with an isolation level such
// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated
// or updated by the mutation.
-func (m *RedeemCodeMutation) IDs(ctx context.Context) ([]int64, error) {
+func (m *SecuritySecretMutation) IDs(ctx context.Context) ([]int64, error) {
switch {
case m.op.Is(OpUpdateOne | OpDeleteOne):
id, exists := m.ID()
@@ -14866,540 +20341,616 @@ func (m *RedeemCodeMutation) IDs(ctx context.Context) ([]int64, error) {
}
fallthrough
case m.op.Is(OpUpdate | OpDelete):
- return m.Client().RedeemCode.Query().Where(m.predicates...).IDs(ctx)
+ return m.Client().SecuritySecret.Query().Where(m.predicates...).IDs(ctx)
default:
return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op)
}
}
-// SetCode sets the "code" field.
-func (m *RedeemCodeMutation) SetCode(s string) {
- m.code = &s
+// SetCreatedAt sets the "created_at" field.
+func (m *SecuritySecretMutation) SetCreatedAt(t time.Time) {
+ m.created_at = &t
}
-// Code returns the value of the "code" field in the mutation.
-func (m *RedeemCodeMutation) Code() (r string, exists bool) {
- v := m.code
+// CreatedAt returns the value of the "created_at" field in the mutation.
+func (m *SecuritySecretMutation) CreatedAt() (r time.Time, exists bool) {
+ v := m.created_at
if v == nil {
return
}
return *v, true
}
-// OldCode returns the old "code" field's value of the RedeemCode entity.
-// If the RedeemCode object wasn't provided to the builder, the object is fetched from the database.
+// OldCreatedAt returns the old "created_at" field's value of the SecuritySecret entity.
+// If the SecuritySecret object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
-func (m *RedeemCodeMutation) OldCode(ctx context.Context) (v string, err error) {
+func (m *SecuritySecretMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) {
if !m.op.Is(OpUpdateOne) {
- return v, errors.New("OldCode is only allowed on UpdateOne operations")
+ return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
- return v, errors.New("OldCode requires an ID field in the mutation")
+ return v, errors.New("OldCreatedAt requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
- return v, fmt.Errorf("querying old value for OldCode: %w", err)
+ return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err)
}
- return oldValue.Code, nil
+ return oldValue.CreatedAt, nil
}
-// ResetCode resets all changes to the "code" field.
-func (m *RedeemCodeMutation) ResetCode() {
- m.code = nil
+// ResetCreatedAt resets all changes to the "created_at" field.
+func (m *SecuritySecretMutation) ResetCreatedAt() {
+ m.created_at = nil
}
-// SetType sets the "type" field.
-func (m *RedeemCodeMutation) SetType(s string) {
- m._type = &s
+// SetUpdatedAt sets the "updated_at" field.
+func (m *SecuritySecretMutation) SetUpdatedAt(t time.Time) {
+ m.updated_at = &t
}
-// GetType returns the value of the "type" field in the mutation.
-func (m *RedeemCodeMutation) GetType() (r string, exists bool) {
- v := m._type
+// UpdatedAt returns the value of the "updated_at" field in the mutation.
+func (m *SecuritySecretMutation) UpdatedAt() (r time.Time, exists bool) {
+ v := m.updated_at
if v == nil {
return
}
return *v, true
}
-// OldType returns the old "type" field's value of the RedeemCode entity.
-// If the RedeemCode object wasn't provided to the builder, the object is fetched from the database.
+// OldUpdatedAt returns the old "updated_at" field's value of the SecuritySecret entity.
+// If the SecuritySecret object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
-func (m *RedeemCodeMutation) OldType(ctx context.Context) (v string, err error) {
+func (m *SecuritySecretMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) {
if !m.op.Is(OpUpdateOne) {
- return v, errors.New("OldType is only allowed on UpdateOne operations")
+ return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
- return v, errors.New("OldType requires an ID field in the mutation")
+ return v, errors.New("OldUpdatedAt requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
- return v, fmt.Errorf("querying old value for OldType: %w", err)
+ return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err)
}
- return oldValue.Type, nil
+ return oldValue.UpdatedAt, nil
}
-// ResetType resets all changes to the "type" field.
-func (m *RedeemCodeMutation) ResetType() {
- m._type = nil
+// ResetUpdatedAt resets all changes to the "updated_at" field.
+func (m *SecuritySecretMutation) ResetUpdatedAt() {
+ m.updated_at = nil
}
-// SetValue sets the "value" field.
-func (m *RedeemCodeMutation) SetValue(f float64) {
- m.value = &f
- m.addvalue = nil
+// SetKey sets the "key" field.
+func (m *SecuritySecretMutation) SetKey(s string) {
+ m.key = &s
}
-// Value returns the value of the "value" field in the mutation.
-func (m *RedeemCodeMutation) Value() (r float64, exists bool) {
- v := m.value
+// Key returns the value of the "key" field in the mutation.
+func (m *SecuritySecretMutation) Key() (r string, exists bool) {
+ v := m.key
if v == nil {
return
}
return *v, true
}
-// OldValue returns the old "value" field's value of the RedeemCode entity.
-// If the RedeemCode object wasn't provided to the builder, the object is fetched from the database.
+// OldKey returns the old "key" field's value of the SecuritySecret entity.
+// If the SecuritySecret object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
-func (m *RedeemCodeMutation) OldValue(ctx context.Context) (v float64, err error) {
+func (m *SecuritySecretMutation) OldKey(ctx context.Context) (v string, err error) {
if !m.op.Is(OpUpdateOne) {
- return v, errors.New("OldValue is only allowed on UpdateOne operations")
+ return v, errors.New("OldKey is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
- return v, errors.New("OldValue requires an ID field in the mutation")
+ return v, errors.New("OldKey requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
- return v, fmt.Errorf("querying old value for OldValue: %w", err)
- }
- return oldValue.Value, nil
-}
-
-// AddValue adds f to the "value" field.
-func (m *RedeemCodeMutation) AddValue(f float64) {
- if m.addvalue != nil {
- *m.addvalue += f
- } else {
- m.addvalue = &f
- }
-}
-
-// AddedValue returns the value that was added to the "value" field in this mutation.
-func (m *RedeemCodeMutation) AddedValue() (r float64, exists bool) {
- v := m.addvalue
- if v == nil {
- return
+ return v, fmt.Errorf("querying old value for OldKey: %w", err)
}
- return *v, true
+ return oldValue.Key, nil
}
-// ResetValue resets all changes to the "value" field.
-func (m *RedeemCodeMutation) ResetValue() {
- m.value = nil
- m.addvalue = nil
+// ResetKey resets all changes to the "key" field.
+func (m *SecuritySecretMutation) ResetKey() {
+ m.key = nil
}
-// SetStatus sets the "status" field.
-func (m *RedeemCodeMutation) SetStatus(s string) {
- m.status = &s
+// SetValue sets the "value" field.
+func (m *SecuritySecretMutation) SetValue(s string) {
+ m.value = &s
}
-// Status returns the value of the "status" field in the mutation.
-func (m *RedeemCodeMutation) Status() (r string, exists bool) {
- v := m.status
+// Value returns the value of the "value" field in the mutation.
+func (m *SecuritySecretMutation) Value() (r string, exists bool) {
+ v := m.value
if v == nil {
return
}
return *v, true
}
-// OldStatus returns the old "status" field's value of the RedeemCode entity.
-// If the RedeemCode object wasn't provided to the builder, the object is fetched from the database.
+// OldValue returns the old "value" field's value of the SecuritySecret entity.
+// If the SecuritySecret object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
-func (m *RedeemCodeMutation) OldStatus(ctx context.Context) (v string, err error) {
+func (m *SecuritySecretMutation) OldValue(ctx context.Context) (v string, err error) {
if !m.op.Is(OpUpdateOne) {
- return v, errors.New("OldStatus is only allowed on UpdateOne operations")
+ return v, errors.New("OldValue is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
- return v, errors.New("OldStatus requires an ID field in the mutation")
+ return v, errors.New("OldValue requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
- return v, fmt.Errorf("querying old value for OldStatus: %w", err)
+ return v, fmt.Errorf("querying old value for OldValue: %w", err)
+ }
+ return oldValue.Value, nil
+}
+
+// ResetValue resets all changes to the "value" field.
+func (m *SecuritySecretMutation) ResetValue() {
+ m.value = nil
+}
+
+// Where appends a list predicates to the SecuritySecretMutation builder.
+func (m *SecuritySecretMutation) Where(ps ...predicate.SecuritySecret) {
+ m.predicates = append(m.predicates, ps...)
+}
+
+// WhereP appends storage-level predicates to the SecuritySecretMutation builder. Using this method,
+// users can use type-assertion to append predicates that do not depend on any generated package.
+func (m *SecuritySecretMutation) WhereP(ps ...func(*sql.Selector)) {
+ p := make([]predicate.SecuritySecret, len(ps))
+ for i := range ps {
+ p[i] = ps[i]
}
- return oldValue.Status, nil
+ m.Where(p...)
}
-// ResetStatus resets all changes to the "status" field.
-func (m *RedeemCodeMutation) ResetStatus() {
- m.status = nil
+// Op returns the operation name.
+func (m *SecuritySecretMutation) Op() Op {
+ return m.op
}
-// SetUsedBy sets the "used_by" field.
-func (m *RedeemCodeMutation) SetUsedBy(i int64) {
- m.user = &i
+// SetOp allows setting the mutation operation.
+func (m *SecuritySecretMutation) SetOp(op Op) {
+ m.op = op
}
-// UsedBy returns the value of the "used_by" field in the mutation.
-func (m *RedeemCodeMutation) UsedBy() (r int64, exists bool) {
- v := m.user
- if v == nil {
- return
- }
- return *v, true
+// Type returns the node type of this mutation (SecuritySecret).
+func (m *SecuritySecretMutation) Type() string {
+ return m.typ
}
-// OldUsedBy returns the old "used_by" field's value of the RedeemCode entity.
-// If the RedeemCode object wasn't provided to the builder, the object is fetched from the database.
-// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
-func (m *RedeemCodeMutation) OldUsedBy(ctx context.Context) (v *int64, err error) {
- if !m.op.Is(OpUpdateOne) {
- return v, errors.New("OldUsedBy is only allowed on UpdateOne operations")
+// Fields returns all fields that were changed during this mutation. Note that in
+// order to get all numeric fields that were incremented/decremented, call
+// AddedFields().
+func (m *SecuritySecretMutation) Fields() []string {
+ fields := make([]string, 0, 4)
+ if m.created_at != nil {
+ fields = append(fields, securitysecret.FieldCreatedAt)
}
- if m.id == nil || m.oldValue == nil {
- return v, errors.New("OldUsedBy requires an ID field in the mutation")
+ if m.updated_at != nil {
+ fields = append(fields, securitysecret.FieldUpdatedAt)
}
- oldValue, err := m.oldValue(ctx)
- if err != nil {
- return v, fmt.Errorf("querying old value for OldUsedBy: %w", err)
+ if m.key != nil {
+ fields = append(fields, securitysecret.FieldKey)
}
- return oldValue.UsedBy, nil
+ if m.value != nil {
+ fields = append(fields, securitysecret.FieldValue)
+ }
+ return fields
}
-// ClearUsedBy clears the value of the "used_by" field.
-func (m *RedeemCodeMutation) ClearUsedBy() {
- m.user = nil
- m.clearedFields[redeemcode.FieldUsedBy] = struct{}{}
+// Field returns the value of a field with the given name. The second boolean
+// return value indicates that this field was not set, or was not defined in the
+// schema.
+func (m *SecuritySecretMutation) Field(name string) (ent.Value, bool) {
+ switch name {
+ case securitysecret.FieldCreatedAt:
+ return m.CreatedAt()
+ case securitysecret.FieldUpdatedAt:
+ return m.UpdatedAt()
+ case securitysecret.FieldKey:
+ return m.Key()
+ case securitysecret.FieldValue:
+ return m.Value()
+ }
+ return nil, false
}
-// UsedByCleared returns if the "used_by" field was cleared in this mutation.
-func (m *RedeemCodeMutation) UsedByCleared() bool {
- _, ok := m.clearedFields[redeemcode.FieldUsedBy]
- return ok
+// OldField returns the old value of the field from the database. An error is
+// returned if the mutation operation is not UpdateOne, or the query to the
+// database failed.
+func (m *SecuritySecretMutation) OldField(ctx context.Context, name string) (ent.Value, error) {
+ switch name {
+ case securitysecret.FieldCreatedAt:
+ return m.OldCreatedAt(ctx)
+ case securitysecret.FieldUpdatedAt:
+ return m.OldUpdatedAt(ctx)
+ case securitysecret.FieldKey:
+ return m.OldKey(ctx)
+ case securitysecret.FieldValue:
+ return m.OldValue(ctx)
+ }
+ return nil, fmt.Errorf("unknown SecuritySecret field %s", name)
}
-// ResetUsedBy resets all changes to the "used_by" field.
-func (m *RedeemCodeMutation) ResetUsedBy() {
- m.user = nil
- delete(m.clearedFields, redeemcode.FieldUsedBy)
+// SetField sets the value of a field with the given name. It returns an error if
+// the field is not defined in the schema, or if the type mismatched the field
+// type.
+func (m *SecuritySecretMutation) SetField(name string, value ent.Value) error {
+ switch name {
+ case securitysecret.FieldCreatedAt:
+ v, ok := value.(time.Time)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetCreatedAt(v)
+ return nil
+ case securitysecret.FieldUpdatedAt:
+ v, ok := value.(time.Time)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetUpdatedAt(v)
+ return nil
+ case securitysecret.FieldKey:
+ v, ok := value.(string)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetKey(v)
+ return nil
+ case securitysecret.FieldValue:
+ v, ok := value.(string)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetValue(v)
+ return nil
+ }
+ return fmt.Errorf("unknown SecuritySecret field %s", name)
}
-// SetUsedAt sets the "used_at" field.
-func (m *RedeemCodeMutation) SetUsedAt(t time.Time) {
- m.used_at = &t
+// AddedFields returns all numeric fields that were incremented/decremented during
+// this mutation.
+func (m *SecuritySecretMutation) AddedFields() []string {
+ return nil
}
-// UsedAt returns the value of the "used_at" field in the mutation.
-func (m *RedeemCodeMutation) UsedAt() (r time.Time, exists bool) {
- v := m.used_at
- if v == nil {
- return
- }
- return *v, true
+// AddedField returns the numeric value that was incremented/decremented on a field
+// with the given name. The second boolean return value indicates that this field
+// was not set, or was not defined in the schema.
+func (m *SecuritySecretMutation) AddedField(name string) (ent.Value, bool) {
+ return nil, false
}
-// OldUsedAt returns the old "used_at" field's value of the RedeemCode entity.
-// If the RedeemCode object wasn't provided to the builder, the object is fetched from the database.
-// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
-func (m *RedeemCodeMutation) OldUsedAt(ctx context.Context) (v *time.Time, err error) {
- if !m.op.Is(OpUpdateOne) {
- return v, errors.New("OldUsedAt is only allowed on UpdateOne operations")
- }
- if m.id == nil || m.oldValue == nil {
- return v, errors.New("OldUsedAt requires an ID field in the mutation")
- }
- oldValue, err := m.oldValue(ctx)
- if err != nil {
- return v, fmt.Errorf("querying old value for OldUsedAt: %w", err)
+// AddField adds the value to the field with the given name. It returns an error if
+// the field is not defined in the schema, or if the type mismatched the field
+// type.
+func (m *SecuritySecretMutation) AddField(name string, value ent.Value) error {
+ switch name {
}
- return oldValue.UsedAt, nil
+ return fmt.Errorf("unknown SecuritySecret numeric field %s", name)
}
-// ClearUsedAt clears the value of the "used_at" field.
-func (m *RedeemCodeMutation) ClearUsedAt() {
- m.used_at = nil
- m.clearedFields[redeemcode.FieldUsedAt] = struct{}{}
+// ClearedFields returns all nullable fields that were cleared during this
+// mutation.
+func (m *SecuritySecretMutation) ClearedFields() []string {
+ return nil
}
-// UsedAtCleared returns if the "used_at" field was cleared in this mutation.
-func (m *RedeemCodeMutation) UsedAtCleared() bool {
- _, ok := m.clearedFields[redeemcode.FieldUsedAt]
+// FieldCleared returns a boolean indicating if a field with the given name was
+// cleared in this mutation.
+func (m *SecuritySecretMutation) FieldCleared(name string) bool {
+ _, ok := m.clearedFields[name]
return ok
}
-// ResetUsedAt resets all changes to the "used_at" field.
-func (m *RedeemCodeMutation) ResetUsedAt() {
- m.used_at = nil
- delete(m.clearedFields, redeemcode.FieldUsedAt)
+// ClearField clears the value of the field with the given name. It returns an
+// error if the field is not defined in the schema.
+func (m *SecuritySecretMutation) ClearField(name string) error {
+ return fmt.Errorf("unknown SecuritySecret nullable field %s", name)
}
-// SetNotes sets the "notes" field.
-func (m *RedeemCodeMutation) SetNotes(s string) {
- m.notes = &s
+// ResetField resets all changes in the mutation for the field with the given name.
+// It returns an error if the field is not defined in the schema.
+func (m *SecuritySecretMutation) ResetField(name string) error {
+ switch name {
+ case securitysecret.FieldCreatedAt:
+ m.ResetCreatedAt()
+ return nil
+ case securitysecret.FieldUpdatedAt:
+ m.ResetUpdatedAt()
+ return nil
+ case securitysecret.FieldKey:
+ m.ResetKey()
+ return nil
+ case securitysecret.FieldValue:
+ m.ResetValue()
+ return nil
+ }
+ return fmt.Errorf("unknown SecuritySecret field %s", name)
}
-// Notes returns the value of the "notes" field in the mutation.
-func (m *RedeemCodeMutation) Notes() (r string, exists bool) {
- v := m.notes
- if v == nil {
- return
- }
- return *v, true
+// AddedEdges returns all edge names that were set/added in this mutation.
+func (m *SecuritySecretMutation) AddedEdges() []string {
+ edges := make([]string, 0, 0)
+ return edges
}
-// OldNotes returns the old "notes" field's value of the RedeemCode entity.
-// If the RedeemCode object wasn't provided to the builder, the object is fetched from the database.
-// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
-func (m *RedeemCodeMutation) OldNotes(ctx context.Context) (v *string, err error) {
- if !m.op.Is(OpUpdateOne) {
- return v, errors.New("OldNotes is only allowed on UpdateOne operations")
- }
- if m.id == nil || m.oldValue == nil {
- return v, errors.New("OldNotes requires an ID field in the mutation")
- }
- oldValue, err := m.oldValue(ctx)
- if err != nil {
- return v, fmt.Errorf("querying old value for OldNotes: %w", err)
- }
- return oldValue.Notes, nil
+// AddedIDs returns all IDs (to other nodes) that were added for the given edge
+// name in this mutation.
+func (m *SecuritySecretMutation) AddedIDs(name string) []ent.Value {
+ return nil
+}
+
+// RemovedEdges returns all edge names that were removed in this mutation.
+func (m *SecuritySecretMutation) RemovedEdges() []string {
+ edges := make([]string, 0, 0)
+ return edges
+}
+
+// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with
+// the given name in this mutation.
+func (m *SecuritySecretMutation) RemovedIDs(name string) []ent.Value {
+ return nil
}
-// ClearNotes clears the value of the "notes" field.
-func (m *RedeemCodeMutation) ClearNotes() {
- m.notes = nil
- m.clearedFields[redeemcode.FieldNotes] = struct{}{}
+// ClearedEdges returns all edge names that were cleared in this mutation.
+func (m *SecuritySecretMutation) ClearedEdges() []string {
+ edges := make([]string, 0, 0)
+ return edges
}
-// NotesCleared returns if the "notes" field was cleared in this mutation.
-func (m *RedeemCodeMutation) NotesCleared() bool {
- _, ok := m.clearedFields[redeemcode.FieldNotes]
- return ok
+// EdgeCleared returns a boolean which indicates if the edge with the given name
+// was cleared in this mutation.
+func (m *SecuritySecretMutation) EdgeCleared(name string) bool {
+ return false
}
-// ResetNotes resets all changes to the "notes" field.
-func (m *RedeemCodeMutation) ResetNotes() {
- m.notes = nil
- delete(m.clearedFields, redeemcode.FieldNotes)
+// ClearEdge clears the value of the edge with the given name. It returns an error
+// if that edge is not defined in the schema.
+func (m *SecuritySecretMutation) ClearEdge(name string) error {
+ return fmt.Errorf("unknown SecuritySecret unique edge %s", name)
}
-// SetCreatedAt sets the "created_at" field.
-func (m *RedeemCodeMutation) SetCreatedAt(t time.Time) {
- m.created_at = &t
+// ResetEdge resets all changes to the edge with the given name in this mutation.
+// It returns an error if the edge is not defined in the schema.
+func (m *SecuritySecretMutation) ResetEdge(name string) error {
+ return fmt.Errorf("unknown SecuritySecret edge %s", name)
}
-// CreatedAt returns the value of the "created_at" field in the mutation.
-func (m *RedeemCodeMutation) CreatedAt() (r time.Time, exists bool) {
- v := m.created_at
- if v == nil {
- return
- }
- return *v, true
+// SettingMutation represents an operation that mutates the Setting nodes in the graph.
+type SettingMutation struct {
+ config
+ op Op
+ typ string
+ id *int64
+ key *string
+ value *string
+ updated_at *time.Time
+ clearedFields map[string]struct{}
+ done bool
+ oldValue func(context.Context) (*Setting, error)
+ predicates []predicate.Setting
}
-// OldCreatedAt returns the old "created_at" field's value of the RedeemCode entity.
-// If the RedeemCode object wasn't provided to the builder, the object is fetched from the database.
-// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
-func (m *RedeemCodeMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) {
- if !m.op.Is(OpUpdateOne) {
- return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations")
- }
- if m.id == nil || m.oldValue == nil {
- return v, errors.New("OldCreatedAt requires an ID field in the mutation")
+var _ ent.Mutation = (*SettingMutation)(nil)
+
+// settingOption allows management of the mutation configuration using functional options.
+type settingOption func(*SettingMutation)
+
+// newSettingMutation creates new mutation for the Setting entity.
+func newSettingMutation(c config, op Op, opts ...settingOption) *SettingMutation {
+ m := &SettingMutation{
+ config: c,
+ op: op,
+ typ: TypeSetting,
+ clearedFields: make(map[string]struct{}),
}
- oldValue, err := m.oldValue(ctx)
- if err != nil {
- return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err)
+ for _, opt := range opts {
+ opt(m)
}
- return oldValue.CreatedAt, nil
-}
-
-// ResetCreatedAt resets all changes to the "created_at" field.
-func (m *RedeemCodeMutation) ResetCreatedAt() {
- m.created_at = nil
+ return m
}
-// SetGroupID sets the "group_id" field.
-func (m *RedeemCodeMutation) SetGroupID(i int64) {
- m.group = &i
+// withSettingID sets the ID field of the mutation.
+func withSettingID(id int64) settingOption {
+ return func(m *SettingMutation) {
+ var (
+ err error
+ once sync.Once
+ value *Setting
+ )
+ m.oldValue = func(ctx context.Context) (*Setting, error) {
+ once.Do(func() {
+ if m.done {
+ err = errors.New("querying old values post mutation is not allowed")
+ } else {
+ value, err = m.Client().Setting.Get(ctx, id)
+ }
+ })
+ return value, err
+ }
+ m.id = &id
+ }
}
-// GroupID returns the value of the "group_id" field in the mutation.
-func (m *RedeemCodeMutation) GroupID() (r int64, exists bool) {
- v := m.group
- if v == nil {
- return
+// withSetting sets the old Setting of the mutation.
+func withSetting(node *Setting) settingOption {
+ return func(m *SettingMutation) {
+ m.oldValue = func(context.Context) (*Setting, error) {
+ return node, nil
+ }
+ m.id = &node.ID
}
- return *v, true
}
-// OldGroupID returns the old "group_id" field's value of the RedeemCode entity.
-// If the RedeemCode object wasn't provided to the builder, the object is fetched from the database.
-// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
-func (m *RedeemCodeMutation) OldGroupID(ctx context.Context) (v *int64, err error) {
- if !m.op.Is(OpUpdateOne) {
- return v, errors.New("OldGroupID is only allowed on UpdateOne operations")
- }
- if m.id == nil || m.oldValue == nil {
- return v, errors.New("OldGroupID requires an ID field in the mutation")
- }
- oldValue, err := m.oldValue(ctx)
- if err != nil {
- return v, fmt.Errorf("querying old value for OldGroupID: %w", err)
- }
- return oldValue.GroupID, nil
+// Client returns a new `ent.Client` from the mutation. If the mutation was
+// executed in a transaction (ent.Tx), a transactional client is returned.
+func (m SettingMutation) Client() *Client {
+ client := &Client{config: m.config}
+ client.init()
+ return client
}
-// ClearGroupID clears the value of the "group_id" field.
-func (m *RedeemCodeMutation) ClearGroupID() {
- m.group = nil
- m.clearedFields[redeemcode.FieldGroupID] = struct{}{}
+// Tx returns an `ent.Tx` for mutations that were executed in transactions;
+// it returns an error otherwise.
+func (m SettingMutation) Tx() (*Tx, error) {
+ if _, ok := m.driver.(*txDriver); !ok {
+ return nil, errors.New("ent: mutation is not running in a transaction")
+ }
+ tx := &Tx{config: m.config}
+ tx.init()
+ return tx, nil
}
-// GroupIDCleared returns if the "group_id" field was cleared in this mutation.
-func (m *RedeemCodeMutation) GroupIDCleared() bool {
- _, ok := m.clearedFields[redeemcode.FieldGroupID]
- return ok
+// ID returns the ID value in the mutation. Note that the ID is only available
+// if it was provided to the builder or after it was returned from the database.
+func (m *SettingMutation) ID() (id int64, exists bool) {
+ if m.id == nil {
+ return
+ }
+ return *m.id, true
}
-// ResetGroupID resets all changes to the "group_id" field.
-func (m *RedeemCodeMutation) ResetGroupID() {
- m.group = nil
- delete(m.clearedFields, redeemcode.FieldGroupID)
+// IDs queries the database and returns the entity ids that match the mutation's predicate.
+// That means, if the mutation is applied within a transaction with an isolation level such
+// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated
+// or updated by the mutation.
+func (m *SettingMutation) IDs(ctx context.Context) ([]int64, error) {
+ switch {
+ case m.op.Is(OpUpdateOne | OpDeleteOne):
+ id, exists := m.ID()
+ if exists {
+ return []int64{id}, nil
+ }
+ fallthrough
+ case m.op.Is(OpUpdate | OpDelete):
+ return m.Client().Setting.Query().Where(m.predicates...).IDs(ctx)
+ default:
+ return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op)
+ }
}
-// SetValidityDays sets the "validity_days" field.
-func (m *RedeemCodeMutation) SetValidityDays(i int) {
- m.validity_days = &i
- m.addvalidity_days = nil
+// SetKey sets the "key" field.
+func (m *SettingMutation) SetKey(s string) {
+ m.key = &s
}
-// ValidityDays returns the value of the "validity_days" field in the mutation.
-func (m *RedeemCodeMutation) ValidityDays() (r int, exists bool) {
- v := m.validity_days
+// Key returns the value of the "key" field in the mutation.
+func (m *SettingMutation) Key() (r string, exists bool) {
+ v := m.key
if v == nil {
return
}
return *v, true
}
-// OldValidityDays returns the old "validity_days" field's value of the RedeemCode entity.
-// If the RedeemCode object wasn't provided to the builder, the object is fetched from the database.
+// OldKey returns the old "key" field's value of the Setting entity.
+// If the Setting object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
-func (m *RedeemCodeMutation) OldValidityDays(ctx context.Context) (v int, err error) {
+func (m *SettingMutation) OldKey(ctx context.Context) (v string, err error) {
if !m.op.Is(OpUpdateOne) {
- return v, errors.New("OldValidityDays is only allowed on UpdateOne operations")
+ return v, errors.New("OldKey is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
- return v, errors.New("OldValidityDays requires an ID field in the mutation")
+ return v, errors.New("OldKey requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
- return v, fmt.Errorf("querying old value for OldValidityDays: %w", err)
- }
- return oldValue.ValidityDays, nil
-}
-
-// AddValidityDays adds i to the "validity_days" field.
-func (m *RedeemCodeMutation) AddValidityDays(i int) {
- if m.addvalidity_days != nil {
- *m.addvalidity_days += i
- } else {
- m.addvalidity_days = &i
- }
-}
-
-// AddedValidityDays returns the value that was added to the "validity_days" field in this mutation.
-func (m *RedeemCodeMutation) AddedValidityDays() (r int, exists bool) {
- v := m.addvalidity_days
- if v == nil {
- return
+ return v, fmt.Errorf("querying old value for OldKey: %w", err)
}
- return *v, true
-}
-
-// ResetValidityDays resets all changes to the "validity_days" field.
-func (m *RedeemCodeMutation) ResetValidityDays() {
- m.validity_days = nil
- m.addvalidity_days = nil
-}
-
-// SetUserID sets the "user" edge to the User entity by id.
-func (m *RedeemCodeMutation) SetUserID(id int64) {
- m.user = &id
-}
-
-// ClearUser clears the "user" edge to the User entity.
-func (m *RedeemCodeMutation) ClearUser() {
- m.cleareduser = true
- m.clearedFields[redeemcode.FieldUsedBy] = struct{}{}
+ return oldValue.Key, nil
}
-// UserCleared reports if the "user" edge to the User entity was cleared.
-func (m *RedeemCodeMutation) UserCleared() bool {
- return m.UsedByCleared() || m.cleareduser
+// ResetKey resets all changes to the "key" field.
+func (m *SettingMutation) ResetKey() {
+ m.key = nil
}
-// UserID returns the "user" edge ID in the mutation.
-func (m *RedeemCodeMutation) UserID() (id int64, exists bool) {
- if m.user != nil {
- return *m.user, true
+// SetValue sets the "value" field.
+func (m *SettingMutation) SetValue(s string) {
+ m.value = &s
+}
+
+// Value returns the value of the "value" field in the mutation.
+func (m *SettingMutation) Value() (r string, exists bool) {
+ v := m.value
+ if v == nil {
+ return
}
- return
+ return *v, true
}
-// UserIDs returns the "user" edge IDs in the mutation.
-// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use
-// UserID instead. It exists only for internal usage by the builders.
-func (m *RedeemCodeMutation) UserIDs() (ids []int64) {
- if id := m.user; id != nil {
- ids = append(ids, *id)
+// OldValue returns the old "value" field's value of the Setting entity.
+// If the Setting object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *SettingMutation) OldValue(ctx context.Context) (v string, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldValue is only allowed on UpdateOne operations")
}
- return
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldValue requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldValue: %w", err)
+ }
+ return oldValue.Value, nil
}
-// ResetUser resets all changes to the "user" edge.
-func (m *RedeemCodeMutation) ResetUser() {
- m.user = nil
- m.cleareduser = false
+// ResetValue resets all changes to the "value" field.
+func (m *SettingMutation) ResetValue() {
+ m.value = nil
}
-// ClearGroup clears the "group" edge to the Group entity.
-func (m *RedeemCodeMutation) ClearGroup() {
- m.clearedgroup = true
- m.clearedFields[redeemcode.FieldGroupID] = struct{}{}
+// SetUpdatedAt sets the "updated_at" field.
+func (m *SettingMutation) SetUpdatedAt(t time.Time) {
+ m.updated_at = &t
}
-// GroupCleared reports if the "group" edge to the Group entity was cleared.
-func (m *RedeemCodeMutation) GroupCleared() bool {
- return m.GroupIDCleared() || m.clearedgroup
+// UpdatedAt returns the value of the "updated_at" field in the mutation.
+func (m *SettingMutation) UpdatedAt() (r time.Time, exists bool) {
+ v := m.updated_at
+ if v == nil {
+ return
+ }
+ return *v, true
}
-// GroupIDs returns the "group" edge IDs in the mutation.
-// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use
-// GroupID instead. It exists only for internal usage by the builders.
-func (m *RedeemCodeMutation) GroupIDs() (ids []int64) {
- if id := m.group; id != nil {
- ids = append(ids, *id)
+// OldUpdatedAt returns the old "updated_at" field's value of the Setting entity.
+// If the Setting object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *SettingMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations")
}
- return
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldUpdatedAt requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err)
+ }
+ return oldValue.UpdatedAt, nil
}
-// ResetGroup resets all changes to the "group" edge.
-func (m *RedeemCodeMutation) ResetGroup() {
- m.group = nil
- m.clearedgroup = false
+// ResetUpdatedAt resets all changes to the "updated_at" field.
+func (m *SettingMutation) ResetUpdatedAt() {
+ m.updated_at = nil
}
-// Where appends a list predicates to the RedeemCodeMutation builder.
-func (m *RedeemCodeMutation) Where(ps ...predicate.RedeemCode) {
+// Where appends a list predicates to the SettingMutation builder.
+func (m *SettingMutation) Where(ps ...predicate.Setting) {
m.predicates = append(m.predicates, ps...)
}
-// WhereP appends storage-level predicates to the RedeemCodeMutation builder. Using this method,
+// WhereP appends storage-level predicates to the SettingMutation builder. Using this method,
// users can use type-assertion to append predicates that do not depend on any generated package.
-func (m *RedeemCodeMutation) WhereP(ps ...func(*sql.Selector)) {
- p := make([]predicate.RedeemCode, len(ps))
+func (m *SettingMutation) WhereP(ps ...func(*sql.Selector)) {
+ p := make([]predicate.Setting, len(ps))
for i := range ps {
p[i] = ps[i]
}
@@ -15407,54 +20958,33 @@ func (m *RedeemCodeMutation) WhereP(ps ...func(*sql.Selector)) {
}
// Op returns the operation name.
-func (m *RedeemCodeMutation) Op() Op {
+func (m *SettingMutation) Op() Op {
return m.op
}
// SetOp allows setting the mutation operation.
-func (m *RedeemCodeMutation) SetOp(op Op) {
+func (m *SettingMutation) SetOp(op Op) {
m.op = op
}
-// Type returns the node type of this mutation (RedeemCode).
-func (m *RedeemCodeMutation) Type() string {
+// Type returns the node type of this mutation (Setting).
+func (m *SettingMutation) Type() string {
return m.typ
}
// Fields returns all fields that were changed during this mutation. Note that in
// order to get all numeric fields that were incremented/decremented, call
// AddedFields().
-func (m *RedeemCodeMutation) Fields() []string {
- fields := make([]string, 0, 10)
- if m.code != nil {
- fields = append(fields, redeemcode.FieldCode)
- }
- if m._type != nil {
- fields = append(fields, redeemcode.FieldType)
+func (m *SettingMutation) Fields() []string {
+ fields := make([]string, 0, 3)
+ if m.key != nil {
+ fields = append(fields, setting.FieldKey)
}
if m.value != nil {
- fields = append(fields, redeemcode.FieldValue)
- }
- if m.status != nil {
- fields = append(fields, redeemcode.FieldStatus)
- }
- if m.user != nil {
- fields = append(fields, redeemcode.FieldUsedBy)
- }
- if m.used_at != nil {
- fields = append(fields, redeemcode.FieldUsedAt)
- }
- if m.notes != nil {
- fields = append(fields, redeemcode.FieldNotes)
- }
- if m.created_at != nil {
- fields = append(fields, redeemcode.FieldCreatedAt)
- }
- if m.group != nil {
- fields = append(fields, redeemcode.FieldGroupID)
+ fields = append(fields, setting.FieldValue)
}
- if m.validity_days != nil {
- fields = append(fields, redeemcode.FieldValidityDays)
+ if m.updated_at != nil {
+ fields = append(fields, setting.FieldUpdatedAt)
}
return fields
}
@@ -15462,28 +20992,14 @@ func (m *RedeemCodeMutation) Fields() []string {
// Field returns the value of a field with the given name. The second boolean
// return value indicates that this field was not set, or was not defined in the
// schema.
-func (m *RedeemCodeMutation) Field(name string) (ent.Value, bool) {
+func (m *SettingMutation) Field(name string) (ent.Value, bool) {
switch name {
- case redeemcode.FieldCode:
- return m.Code()
- case redeemcode.FieldType:
- return m.GetType()
- case redeemcode.FieldValue:
+ case setting.FieldKey:
+ return m.Key()
+ case setting.FieldValue:
return m.Value()
- case redeemcode.FieldStatus:
- return m.Status()
- case redeemcode.FieldUsedBy:
- return m.UsedBy()
- case redeemcode.FieldUsedAt:
- return m.UsedAt()
- case redeemcode.FieldNotes:
- return m.Notes()
- case redeemcode.FieldCreatedAt:
- return m.CreatedAt()
- case redeemcode.FieldGroupID:
- return m.GroupID()
- case redeemcode.FieldValidityDays:
- return m.ValidityDays()
+ case setting.FieldUpdatedAt:
+ return m.UpdatedAt()
}
return nil, false
}
@@ -15491,363 +21007,195 @@ func (m *RedeemCodeMutation) Field(name string) (ent.Value, bool) {
// OldField returns the old value of the field from the database. An error is
// returned if the mutation operation is not UpdateOne, or the query to the
// database failed.
-func (m *RedeemCodeMutation) OldField(ctx context.Context, name string) (ent.Value, error) {
+func (m *SettingMutation) OldField(ctx context.Context, name string) (ent.Value, error) {
switch name {
- case redeemcode.FieldCode:
- return m.OldCode(ctx)
- case redeemcode.FieldType:
- return m.OldType(ctx)
- case redeemcode.FieldValue:
+ case setting.FieldKey:
+ return m.OldKey(ctx)
+ case setting.FieldValue:
return m.OldValue(ctx)
- case redeemcode.FieldStatus:
- return m.OldStatus(ctx)
- case redeemcode.FieldUsedBy:
- return m.OldUsedBy(ctx)
- case redeemcode.FieldUsedAt:
- return m.OldUsedAt(ctx)
- case redeemcode.FieldNotes:
- return m.OldNotes(ctx)
- case redeemcode.FieldCreatedAt:
- return m.OldCreatedAt(ctx)
- case redeemcode.FieldGroupID:
- return m.OldGroupID(ctx)
- case redeemcode.FieldValidityDays:
- return m.OldValidityDays(ctx)
+ case setting.FieldUpdatedAt:
+ return m.OldUpdatedAt(ctx)
}
- return nil, fmt.Errorf("unknown RedeemCode field %s", name)
+ return nil, fmt.Errorf("unknown Setting field %s", name)
}
// SetField sets the value of a field with the given name. It returns an error if
// the field is not defined in the schema, or if the type mismatched the field
// type.
-func (m *RedeemCodeMutation) SetField(name string, value ent.Value) error {
+func (m *SettingMutation) SetField(name string, value ent.Value) error {
switch name {
- case redeemcode.FieldCode:
+ case setting.FieldKey:
v, ok := value.(string)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
- m.SetCode(v)
+ m.SetKey(v)
return nil
- case redeemcode.FieldType:
+ case setting.FieldValue:
v, ok := value.(string)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
- m.SetType(v)
- return nil
- case redeemcode.FieldValue:
- v, ok := value.(float64)
- if !ok {
- return fmt.Errorf("unexpected type %T for field %s", value, name)
- }
m.SetValue(v)
- return nil
- case redeemcode.FieldStatus:
- v, ok := value.(string)
- if !ok {
- return fmt.Errorf("unexpected type %T for field %s", value, name)
- }
- m.SetStatus(v)
- return nil
- case redeemcode.FieldUsedBy:
- v, ok := value.(int64)
- if !ok {
- return fmt.Errorf("unexpected type %T for field %s", value, name)
- }
- m.SetUsedBy(v)
- return nil
- case redeemcode.FieldUsedAt:
- v, ok := value.(time.Time)
- if !ok {
- return fmt.Errorf("unexpected type %T for field %s", value, name)
- }
- m.SetUsedAt(v)
- return nil
- case redeemcode.FieldNotes:
- v, ok := value.(string)
- if !ok {
- return fmt.Errorf("unexpected type %T for field %s", value, name)
- }
- m.SetNotes(v)
- return nil
- case redeemcode.FieldCreatedAt:
- v, ok := value.(time.Time)
- if !ok {
- return fmt.Errorf("unexpected type %T for field %s", value, name)
- }
- m.SetCreatedAt(v)
- return nil
- case redeemcode.FieldGroupID:
- v, ok := value.(int64)
- if !ok {
- return fmt.Errorf("unexpected type %T for field %s", value, name)
- }
- m.SetGroupID(v)
- return nil
- case redeemcode.FieldValidityDays:
- v, ok := value.(int)
+ return nil
+ case setting.FieldUpdatedAt:
+ v, ok := value.(time.Time)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
- m.SetValidityDays(v)
+ m.SetUpdatedAt(v)
return nil
}
- return fmt.Errorf("unknown RedeemCode field %s", name)
+ return fmt.Errorf("unknown Setting field %s", name)
}
// AddedFields returns all numeric fields that were incremented/decremented during
// this mutation.
-func (m *RedeemCodeMutation) AddedFields() []string {
- var fields []string
- if m.addvalue != nil {
- fields = append(fields, redeemcode.FieldValue)
- }
- if m.addvalidity_days != nil {
- fields = append(fields, redeemcode.FieldValidityDays)
- }
- return fields
+func (m *SettingMutation) AddedFields() []string {
+ return nil
}
// AddedField returns the numeric value that was incremented/decremented on a field
// with the given name. The second boolean return value indicates that this field
// was not set, or was not defined in the schema.
-func (m *RedeemCodeMutation) AddedField(name string) (ent.Value, bool) {
- switch name {
- case redeemcode.FieldValue:
- return m.AddedValue()
- case redeemcode.FieldValidityDays:
- return m.AddedValidityDays()
- }
+func (m *SettingMutation) AddedField(name string) (ent.Value, bool) {
return nil, false
}
// AddField adds the value to the field with the given name. It returns an error if
// the field is not defined in the schema, or if the type mismatched the field
// type.
-func (m *RedeemCodeMutation) AddField(name string, value ent.Value) error {
+func (m *SettingMutation) AddField(name string, value ent.Value) error {
switch name {
- case redeemcode.FieldValue:
- v, ok := value.(float64)
- if !ok {
- return fmt.Errorf("unexpected type %T for field %s", value, name)
- }
- m.AddValue(v)
- return nil
- case redeemcode.FieldValidityDays:
- v, ok := value.(int)
- if !ok {
- return fmt.Errorf("unexpected type %T for field %s", value, name)
- }
- m.AddValidityDays(v)
- return nil
}
- return fmt.Errorf("unknown RedeemCode numeric field %s", name)
+ return fmt.Errorf("unknown Setting numeric field %s", name)
}
// ClearedFields returns all nullable fields that were cleared during this
// mutation.
-func (m *RedeemCodeMutation) ClearedFields() []string {
- var fields []string
- if m.FieldCleared(redeemcode.FieldUsedBy) {
- fields = append(fields, redeemcode.FieldUsedBy)
- }
- if m.FieldCleared(redeemcode.FieldUsedAt) {
- fields = append(fields, redeemcode.FieldUsedAt)
- }
- if m.FieldCleared(redeemcode.FieldNotes) {
- fields = append(fields, redeemcode.FieldNotes)
- }
- if m.FieldCleared(redeemcode.FieldGroupID) {
- fields = append(fields, redeemcode.FieldGroupID)
- }
- return fields
+func (m *SettingMutation) ClearedFields() []string {
+ return nil
}
// FieldCleared returns a boolean indicating if a field with the given name was
// cleared in this mutation.
-func (m *RedeemCodeMutation) FieldCleared(name string) bool {
+func (m *SettingMutation) FieldCleared(name string) bool {
_, ok := m.clearedFields[name]
return ok
}
// ClearField clears the value of the field with the given name. It returns an
// error if the field is not defined in the schema.
-func (m *RedeemCodeMutation) ClearField(name string) error {
- switch name {
- case redeemcode.FieldUsedBy:
- m.ClearUsedBy()
- return nil
- case redeemcode.FieldUsedAt:
- m.ClearUsedAt()
- return nil
- case redeemcode.FieldNotes:
- m.ClearNotes()
- return nil
- case redeemcode.FieldGroupID:
- m.ClearGroupID()
- return nil
- }
- return fmt.Errorf("unknown RedeemCode nullable field %s", name)
+func (m *SettingMutation) ClearField(name string) error {
+ return fmt.Errorf("unknown Setting nullable field %s", name)
}
// ResetField resets all changes in the mutation for the field with the given name.
// It returns an error if the field is not defined in the schema.
-func (m *RedeemCodeMutation) ResetField(name string) error {
+func (m *SettingMutation) ResetField(name string) error {
switch name {
- case redeemcode.FieldCode:
- m.ResetCode()
- return nil
- case redeemcode.FieldType:
- m.ResetType()
+ case setting.FieldKey:
+ m.ResetKey()
return nil
- case redeemcode.FieldValue:
+ case setting.FieldValue:
m.ResetValue()
return nil
- case redeemcode.FieldStatus:
- m.ResetStatus()
- return nil
- case redeemcode.FieldUsedBy:
- m.ResetUsedBy()
- return nil
- case redeemcode.FieldUsedAt:
- m.ResetUsedAt()
- return nil
- case redeemcode.FieldNotes:
- m.ResetNotes()
- return nil
- case redeemcode.FieldCreatedAt:
- m.ResetCreatedAt()
- return nil
- case redeemcode.FieldGroupID:
- m.ResetGroupID()
- return nil
- case redeemcode.FieldValidityDays:
- m.ResetValidityDays()
+ case setting.FieldUpdatedAt:
+ m.ResetUpdatedAt()
return nil
}
- return fmt.Errorf("unknown RedeemCode field %s", name)
+ return fmt.Errorf("unknown Setting field %s", name)
}
// AddedEdges returns all edge names that were set/added in this mutation.
-func (m *RedeemCodeMutation) AddedEdges() []string {
- edges := make([]string, 0, 2)
- if m.user != nil {
- edges = append(edges, redeemcode.EdgeUser)
- }
- if m.group != nil {
- edges = append(edges, redeemcode.EdgeGroup)
- }
+func (m *SettingMutation) AddedEdges() []string {
+ edges := make([]string, 0, 0)
return edges
}
// AddedIDs returns all IDs (to other nodes) that were added for the given edge
// name in this mutation.
-func (m *RedeemCodeMutation) AddedIDs(name string) []ent.Value {
- switch name {
- case redeemcode.EdgeUser:
- if id := m.user; id != nil {
- return []ent.Value{*id}
- }
- case redeemcode.EdgeGroup:
- if id := m.group; id != nil {
- return []ent.Value{*id}
- }
- }
+func (m *SettingMutation) AddedIDs(name string) []ent.Value {
return nil
}
// RemovedEdges returns all edge names that were removed in this mutation.
-func (m *RedeemCodeMutation) RemovedEdges() []string {
- edges := make([]string, 0, 2)
+func (m *SettingMutation) RemovedEdges() []string {
+ edges := make([]string, 0, 0)
return edges
}
// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with
// the given name in this mutation.
-func (m *RedeemCodeMutation) RemovedIDs(name string) []ent.Value {
+func (m *SettingMutation) RemovedIDs(name string) []ent.Value {
return nil
}
// ClearedEdges returns all edge names that were cleared in this mutation.
-func (m *RedeemCodeMutation) ClearedEdges() []string {
- edges := make([]string, 0, 2)
- if m.cleareduser {
- edges = append(edges, redeemcode.EdgeUser)
- }
- if m.clearedgroup {
- edges = append(edges, redeemcode.EdgeGroup)
- }
+func (m *SettingMutation) ClearedEdges() []string {
+ edges := make([]string, 0, 0)
return edges
}
// EdgeCleared returns a boolean which indicates if the edge with the given name
// was cleared in this mutation.
-func (m *RedeemCodeMutation) EdgeCleared(name string) bool {
- switch name {
- case redeemcode.EdgeUser:
- return m.cleareduser
- case redeemcode.EdgeGroup:
- return m.clearedgroup
- }
+func (m *SettingMutation) EdgeCleared(name string) bool {
return false
}
// ClearEdge clears the value of the edge with the given name. It returns an error
// if that edge is not defined in the schema.
-func (m *RedeemCodeMutation) ClearEdge(name string) error {
- switch name {
- case redeemcode.EdgeUser:
- m.ClearUser()
- return nil
- case redeemcode.EdgeGroup:
- m.ClearGroup()
- return nil
- }
- return fmt.Errorf("unknown RedeemCode unique edge %s", name)
+func (m *SettingMutation) ClearEdge(name string) error {
+ return fmt.Errorf("unknown Setting unique edge %s", name)
}
// ResetEdge resets all changes to the edge with the given name in this mutation.
// It returns an error if the edge is not defined in the schema.
-func (m *RedeemCodeMutation) ResetEdge(name string) error {
- switch name {
- case redeemcode.EdgeUser:
- m.ResetUser()
- return nil
- case redeemcode.EdgeGroup:
- m.ResetGroup()
- return nil
- }
- return fmt.Errorf("unknown RedeemCode edge %s", name)
+func (m *SettingMutation) ResetEdge(name string) error {
+ return fmt.Errorf("unknown Setting edge %s", name)
}
-// SecuritySecretMutation represents an operation that mutates the SecuritySecret nodes in the graph.
-type SecuritySecretMutation struct {
+// SubscriptionPlanMutation represents an operation that mutates the SubscriptionPlan nodes in the graph.
+type SubscriptionPlanMutation struct {
config
- op Op
- typ string
- id *int64
- created_at *time.Time
- updated_at *time.Time
- key *string
- value *string
- clearedFields map[string]struct{}
- done bool
- oldValue func(context.Context) (*SecuritySecret, error)
- predicates []predicate.SecuritySecret
+ op Op
+ typ string
+ id *int64
+ group_id *int64
+ addgroup_id *int64
+ name *string
+ description *string
+ price *float64
+ addprice *float64
+ original_price *float64
+ addoriginal_price *float64
+ validity_days *int
+ addvalidity_days *int
+ validity_unit *string
+ features *string
+ product_name *string
+ for_sale *bool
+ sort_order *int
+ addsort_order *int
+ created_at *time.Time
+ updated_at *time.Time
+ clearedFields map[string]struct{}
+ done bool
+ oldValue func(context.Context) (*SubscriptionPlan, error)
+ predicates []predicate.SubscriptionPlan
}
-var _ ent.Mutation = (*SecuritySecretMutation)(nil)
+var _ ent.Mutation = (*SubscriptionPlanMutation)(nil)
-// securitysecretOption allows management of the mutation configuration using functional options.
-type securitysecretOption func(*SecuritySecretMutation)
+// subscriptionplanOption allows management of the mutation configuration using functional options.
+type subscriptionplanOption func(*SubscriptionPlanMutation)
-// newSecuritySecretMutation creates new mutation for the SecuritySecret entity.
-func newSecuritySecretMutation(c config, op Op, opts ...securitysecretOption) *SecuritySecretMutation {
- m := &SecuritySecretMutation{
+// newSubscriptionPlanMutation creates new mutation for the SubscriptionPlan entity.
+func newSubscriptionPlanMutation(c config, op Op, opts ...subscriptionplanOption) *SubscriptionPlanMutation {
+ m := &SubscriptionPlanMutation{
config: c,
op: op,
- typ: TypeSecuritySecret,
+ typ: TypeSubscriptionPlan,
clearedFields: make(map[string]struct{}),
}
for _, opt := range opts {
@@ -15856,20 +21204,20 @@ func newSecuritySecretMutation(c config, op Op, opts ...securitysecretOption) *S
return m
}
-// withSecuritySecretID sets the ID field of the mutation.
-func withSecuritySecretID(id int64) securitysecretOption {
- return func(m *SecuritySecretMutation) {
+// withSubscriptionPlanID sets the ID field of the mutation.
+func withSubscriptionPlanID(id int64) subscriptionplanOption {
+ return func(m *SubscriptionPlanMutation) {
var (
err error
once sync.Once
- value *SecuritySecret
+ value *SubscriptionPlan
)
- m.oldValue = func(ctx context.Context) (*SecuritySecret, error) {
+ m.oldValue = func(ctx context.Context) (*SubscriptionPlan, error) {
once.Do(func() {
if m.done {
err = errors.New("querying old values post mutation is not allowed")
} else {
- value, err = m.Client().SecuritySecret.Get(ctx, id)
+ value, err = m.Client().SubscriptionPlan.Get(ctx, id)
}
})
return value, err
@@ -15878,10 +21226,10 @@ func withSecuritySecretID(id int64) securitysecretOption {
}
}
-// withSecuritySecret sets the old SecuritySecret of the mutation.
-func withSecuritySecret(node *SecuritySecret) securitysecretOption {
- return func(m *SecuritySecretMutation) {
- m.oldValue = func(context.Context) (*SecuritySecret, error) {
+// withSubscriptionPlan sets the old SubscriptionPlan of the mutation.
+func withSubscriptionPlan(node *SubscriptionPlan) subscriptionplanOption {
+ return func(m *SubscriptionPlanMutation) {
+ m.oldValue = func(context.Context) (*SubscriptionPlan, error) {
return node, nil
}
m.id = &node.ID
@@ -15890,7 +21238,7 @@ func withSecuritySecret(node *SecuritySecret) securitysecretOption {
// Client returns a new `ent.Client` from the mutation. If the mutation was
// executed in a transaction (ent.Tx), a transactional client is returned.
-func (m SecuritySecretMutation) Client() *Client {
+func (m SubscriptionPlanMutation) Client() *Client {
client := &Client{config: m.config}
client.init()
return client
@@ -15898,7 +21246,7 @@ func (m SecuritySecretMutation) Client() *Client {
// Tx returns an `ent.Tx` for mutations that were executed in transactions;
// it returns an error otherwise.
-func (m SecuritySecretMutation) Tx() (*Tx, error) {
+func (m SubscriptionPlanMutation) Tx() (*Tx, error) {
if _, ok := m.driver.(*txDriver); !ok {
return nil, errors.New("ent: mutation is not running in a transaction")
}
@@ -15909,7 +21257,7 @@ func (m SecuritySecretMutation) Tx() (*Tx, error) {
// ID returns the ID value in the mutation. Note that the ID is only available
// if it was provided to the builder or after it was returned from the database.
-func (m *SecuritySecretMutation) ID() (id int64, exists bool) {
+func (m *SubscriptionPlanMutation) ID() (id int64, exists bool) {
if m.id == nil {
return
}
@@ -15920,7 +21268,7 @@ func (m *SecuritySecretMutation) ID() (id int64, exists bool) {
// That means, if the mutation is applied within a transaction with an isolation level such
// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated
// or updated by the mutation.
-func (m *SecuritySecretMutation) IDs(ctx context.Context) ([]int64, error) {
+func (m *SubscriptionPlanMutation) IDs(ctx context.Context) ([]int64, error) {
switch {
case m.op.Is(OpUpdateOne | OpDeleteOne):
id, exists := m.ID()
@@ -15929,578 +21277,565 @@ func (m *SecuritySecretMutation) IDs(ctx context.Context) ([]int64, error) {
}
fallthrough
case m.op.Is(OpUpdate | OpDelete):
- return m.Client().SecuritySecret.Query().Where(m.predicates...).IDs(ctx)
+ return m.Client().SubscriptionPlan.Query().Where(m.predicates...).IDs(ctx)
default:
return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op)
}
}
-// SetCreatedAt sets the "created_at" field.
-func (m *SecuritySecretMutation) SetCreatedAt(t time.Time) {
- m.created_at = &t
+// SetGroupID sets the "group_id" field.
+func (m *SubscriptionPlanMutation) SetGroupID(i int64) {
+ m.group_id = &i
+ m.addgroup_id = nil
}
-// CreatedAt returns the value of the "created_at" field in the mutation.
-func (m *SecuritySecretMutation) CreatedAt() (r time.Time, exists bool) {
- v := m.created_at
+// GroupID returns the value of the "group_id" field in the mutation.
+func (m *SubscriptionPlanMutation) GroupID() (r int64, exists bool) {
+ v := m.group_id
if v == nil {
return
}
return *v, true
}
-// OldCreatedAt returns the old "created_at" field's value of the SecuritySecret entity.
-// If the SecuritySecret object wasn't provided to the builder, the object is fetched from the database.
+// OldGroupID returns the old "group_id" field's value of the SubscriptionPlan entity.
+// If the SubscriptionPlan object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
-func (m *SecuritySecretMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) {
+func (m *SubscriptionPlanMutation) OldGroupID(ctx context.Context) (v int64, err error) {
if !m.op.Is(OpUpdateOne) {
- return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations")
+ return v, errors.New("OldGroupID is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
- return v, errors.New("OldCreatedAt requires an ID field in the mutation")
+ return v, errors.New("OldGroupID requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
- return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err)
+ return v, fmt.Errorf("querying old value for OldGroupID: %w", err)
+ }
+ return oldValue.GroupID, nil
+}
+
+// AddGroupID adds i to the "group_id" field.
+func (m *SubscriptionPlanMutation) AddGroupID(i int64) {
+ if m.addgroup_id != nil {
+ *m.addgroup_id += i
+ } else {
+ m.addgroup_id = &i
+ }
+}
+
+// AddedGroupID returns the value that was added to the "group_id" field in this mutation.
+func (m *SubscriptionPlanMutation) AddedGroupID() (r int64, exists bool) {
+ v := m.addgroup_id
+ if v == nil {
+ return
}
- return oldValue.CreatedAt, nil
+ return *v, true
}
-// ResetCreatedAt resets all changes to the "created_at" field.
-func (m *SecuritySecretMutation) ResetCreatedAt() {
- m.created_at = nil
+// ResetGroupID resets all changes to the "group_id" field.
+func (m *SubscriptionPlanMutation) ResetGroupID() {
+ m.group_id = nil
+ m.addgroup_id = nil
}
-// SetUpdatedAt sets the "updated_at" field.
-func (m *SecuritySecretMutation) SetUpdatedAt(t time.Time) {
- m.updated_at = &t
+// SetName sets the "name" field.
+func (m *SubscriptionPlanMutation) SetName(s string) {
+ m.name = &s
}
-// UpdatedAt returns the value of the "updated_at" field in the mutation.
-func (m *SecuritySecretMutation) UpdatedAt() (r time.Time, exists bool) {
- v := m.updated_at
+// Name returns the value of the "name" field in the mutation.
+func (m *SubscriptionPlanMutation) Name() (r string, exists bool) {
+ v := m.name
if v == nil {
return
}
return *v, true
}
-// OldUpdatedAt returns the old "updated_at" field's value of the SecuritySecret entity.
-// If the SecuritySecret object wasn't provided to the builder, the object is fetched from the database.
+// OldName returns the old "name" field's value of the SubscriptionPlan entity.
+// If the SubscriptionPlan object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
-func (m *SecuritySecretMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) {
+func (m *SubscriptionPlanMutation) OldName(ctx context.Context) (v string, err error) {
if !m.op.Is(OpUpdateOne) {
- return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations")
+ return v, errors.New("OldName is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
- return v, errors.New("OldUpdatedAt requires an ID field in the mutation")
+ return v, errors.New("OldName requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
- return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err)
+ return v, fmt.Errorf("querying old value for OldName: %w", err)
}
- return oldValue.UpdatedAt, nil
+ return oldValue.Name, nil
}
-// ResetUpdatedAt resets all changes to the "updated_at" field.
-func (m *SecuritySecretMutation) ResetUpdatedAt() {
- m.updated_at = nil
+// ResetName resets all changes to the "name" field.
+func (m *SubscriptionPlanMutation) ResetName() {
+ m.name = nil
}
-// SetKey sets the "key" field.
-func (m *SecuritySecretMutation) SetKey(s string) {
- m.key = &s
+// SetDescription sets the "description" field.
+func (m *SubscriptionPlanMutation) SetDescription(s string) {
+ m.description = &s
}
-// Key returns the value of the "key" field in the mutation.
-func (m *SecuritySecretMutation) Key() (r string, exists bool) {
- v := m.key
+// Description returns the value of the "description" field in the mutation.
+func (m *SubscriptionPlanMutation) Description() (r string, exists bool) {
+ v := m.description
if v == nil {
return
}
return *v, true
}
-// OldKey returns the old "key" field's value of the SecuritySecret entity.
-// If the SecuritySecret object wasn't provided to the builder, the object is fetched from the database.
+// OldDescription returns the old "description" field's value of the SubscriptionPlan entity.
+// If the SubscriptionPlan object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
-func (m *SecuritySecretMutation) OldKey(ctx context.Context) (v string, err error) {
+func (m *SubscriptionPlanMutation) OldDescription(ctx context.Context) (v string, err error) {
if !m.op.Is(OpUpdateOne) {
- return v, errors.New("OldKey is only allowed on UpdateOne operations")
+ return v, errors.New("OldDescription is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
- return v, errors.New("OldKey requires an ID field in the mutation")
+ return v, errors.New("OldDescription requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
- return v, fmt.Errorf("querying old value for OldKey: %w", err)
+ return v, fmt.Errorf("querying old value for OldDescription: %w", err)
}
- return oldValue.Key, nil
+ return oldValue.Description, nil
}
-// ResetKey resets all changes to the "key" field.
-func (m *SecuritySecretMutation) ResetKey() {
- m.key = nil
+// ResetDescription resets all changes to the "description" field.
+func (m *SubscriptionPlanMutation) ResetDescription() {
+ m.description = nil
}
-// SetValue sets the "value" field.
-func (m *SecuritySecretMutation) SetValue(s string) {
- m.value = &s
+// SetPrice sets the "price" field.
+func (m *SubscriptionPlanMutation) SetPrice(f float64) {
+ m.price = &f
+ m.addprice = nil
}
-// Value returns the value of the "value" field in the mutation.
-func (m *SecuritySecretMutation) Value() (r string, exists bool) {
- v := m.value
+// Price returns the value of the "price" field in the mutation.
+func (m *SubscriptionPlanMutation) Price() (r float64, exists bool) {
+ v := m.price
if v == nil {
return
}
return *v, true
}
-// OldValue returns the old "value" field's value of the SecuritySecret entity.
-// If the SecuritySecret object wasn't provided to the builder, the object is fetched from the database.
+// OldPrice returns the old "price" field's value of the SubscriptionPlan entity.
+// If the SubscriptionPlan object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
-func (m *SecuritySecretMutation) OldValue(ctx context.Context) (v string, err error) {
+func (m *SubscriptionPlanMutation) OldPrice(ctx context.Context) (v float64, err error) {
if !m.op.Is(OpUpdateOne) {
- return v, errors.New("OldValue is only allowed on UpdateOne operations")
+ return v, errors.New("OldPrice is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
- return v, errors.New("OldValue requires an ID field in the mutation")
+ return v, errors.New("OldPrice requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
- return v, fmt.Errorf("querying old value for OldValue: %w", err)
+ return v, fmt.Errorf("querying old value for OldPrice: %w", err)
}
- return oldValue.Value, nil
+ return oldValue.Price, nil
}
-// ResetValue resets all changes to the "value" field.
-func (m *SecuritySecretMutation) ResetValue() {
- m.value = nil
-}
-
-// Where appends a list predicates to the SecuritySecretMutation builder.
-func (m *SecuritySecretMutation) Where(ps ...predicate.SecuritySecret) {
- m.predicates = append(m.predicates, ps...)
+// AddPrice adds f to the "price" field.
+func (m *SubscriptionPlanMutation) AddPrice(f float64) {
+ if m.addprice != nil {
+ *m.addprice += f
+ } else {
+ m.addprice = &f
+ }
}
-// WhereP appends storage-level predicates to the SecuritySecretMutation builder. Using this method,
-// users can use type-assertion to append predicates that do not depend on any generated package.
-func (m *SecuritySecretMutation) WhereP(ps ...func(*sql.Selector)) {
- p := make([]predicate.SecuritySecret, len(ps))
- for i := range ps {
- p[i] = ps[i]
+// AddedPrice returns the value that was added to the "price" field in this mutation.
+func (m *SubscriptionPlanMutation) AddedPrice() (r float64, exists bool) {
+ v := m.addprice
+ if v == nil {
+ return
}
- m.Where(p...)
+ return *v, true
}
-// Op returns the operation name.
-func (m *SecuritySecretMutation) Op() Op {
- return m.op
+// ResetPrice resets all changes to the "price" field.
+func (m *SubscriptionPlanMutation) ResetPrice() {
+ m.price = nil
+ m.addprice = nil
}
-// SetOp allows setting the mutation operation.
-func (m *SecuritySecretMutation) SetOp(op Op) {
- m.op = op
+// SetOriginalPrice sets the "original_price" field.
+func (m *SubscriptionPlanMutation) SetOriginalPrice(f float64) {
+ m.original_price = &f
+ m.addoriginal_price = nil
}
-// Type returns the node type of this mutation (SecuritySecret).
-func (m *SecuritySecretMutation) Type() string {
- return m.typ
+// OriginalPrice returns the value of the "original_price" field in the mutation.
+func (m *SubscriptionPlanMutation) OriginalPrice() (r float64, exists bool) {
+ v := m.original_price
+ if v == nil {
+ return
+ }
+ return *v, true
}
-// Fields returns all fields that were changed during this mutation. Note that in
-// order to get all numeric fields that were incremented/decremented, call
-// AddedFields().
-func (m *SecuritySecretMutation) Fields() []string {
- fields := make([]string, 0, 4)
- if m.created_at != nil {
- fields = append(fields, securitysecret.FieldCreatedAt)
- }
- if m.updated_at != nil {
- fields = append(fields, securitysecret.FieldUpdatedAt)
- }
- if m.key != nil {
- fields = append(fields, securitysecret.FieldKey)
+// OldOriginalPrice returns the old "original_price" field's value of the SubscriptionPlan entity.
+// If the SubscriptionPlan object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *SubscriptionPlanMutation) OldOriginalPrice(ctx context.Context) (v *float64, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldOriginalPrice is only allowed on UpdateOne operations")
}
- if m.value != nil {
- fields = append(fields, securitysecret.FieldValue)
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldOriginalPrice requires an ID field in the mutation")
}
- return fields
-}
-
-// Field returns the value of a field with the given name. The second boolean
-// return value indicates that this field was not set, or was not defined in the
-// schema.
-func (m *SecuritySecretMutation) Field(name string) (ent.Value, bool) {
- switch name {
- case securitysecret.FieldCreatedAt:
- return m.CreatedAt()
- case securitysecret.FieldUpdatedAt:
- return m.UpdatedAt()
- case securitysecret.FieldKey:
- return m.Key()
- case securitysecret.FieldValue:
- return m.Value()
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldOriginalPrice: %w", err)
}
- return nil, false
+ return oldValue.OriginalPrice, nil
}
-// OldField returns the old value of the field from the database. An error is
-// returned if the mutation operation is not UpdateOne, or the query to the
-// database failed.
-func (m *SecuritySecretMutation) OldField(ctx context.Context, name string) (ent.Value, error) {
- switch name {
- case securitysecret.FieldCreatedAt:
- return m.OldCreatedAt(ctx)
- case securitysecret.FieldUpdatedAt:
- return m.OldUpdatedAt(ctx)
- case securitysecret.FieldKey:
- return m.OldKey(ctx)
- case securitysecret.FieldValue:
- return m.OldValue(ctx)
+// AddOriginalPrice adds f to the "original_price" field.
+func (m *SubscriptionPlanMutation) AddOriginalPrice(f float64) {
+ if m.addoriginal_price != nil {
+ *m.addoriginal_price += f
+ } else {
+ m.addoriginal_price = &f
}
- return nil, fmt.Errorf("unknown SecuritySecret field %s", name)
}
-// SetField sets the value of a field with the given name. It returns an error if
-// the field is not defined in the schema, or if the type mismatched the field
-// type.
-func (m *SecuritySecretMutation) SetField(name string, value ent.Value) error {
- switch name {
- case securitysecret.FieldCreatedAt:
- v, ok := value.(time.Time)
- if !ok {
- return fmt.Errorf("unexpected type %T for field %s", value, name)
- }
- m.SetCreatedAt(v)
- return nil
- case securitysecret.FieldUpdatedAt:
- v, ok := value.(time.Time)
- if !ok {
- return fmt.Errorf("unexpected type %T for field %s", value, name)
- }
- m.SetUpdatedAt(v)
- return nil
- case securitysecret.FieldKey:
- v, ok := value.(string)
- if !ok {
- return fmt.Errorf("unexpected type %T for field %s", value, name)
- }
- m.SetKey(v)
- return nil
- case securitysecret.FieldValue:
- v, ok := value.(string)
- if !ok {
- return fmt.Errorf("unexpected type %T for field %s", value, name)
- }
- m.SetValue(v)
- return nil
+// AddedOriginalPrice returns the value that was added to the "original_price" field in this mutation.
+func (m *SubscriptionPlanMutation) AddedOriginalPrice() (r float64, exists bool) {
+ v := m.addoriginal_price
+ if v == nil {
+ return
}
- return fmt.Errorf("unknown SecuritySecret field %s", name)
-}
-
-// AddedFields returns all numeric fields that were incremented/decremented during
-// this mutation.
-func (m *SecuritySecretMutation) AddedFields() []string {
- return nil
+ return *v, true
}
-// AddedField returns the numeric value that was incremented/decremented on a field
-// with the given name. The second boolean return value indicates that this field
-// was not set, or was not defined in the schema.
-func (m *SecuritySecretMutation) AddedField(name string) (ent.Value, bool) {
- return nil, false
+// ClearOriginalPrice clears the value of the "original_price" field.
+func (m *SubscriptionPlanMutation) ClearOriginalPrice() {
+ m.original_price = nil
+ m.addoriginal_price = nil
+ m.clearedFields[subscriptionplan.FieldOriginalPrice] = struct{}{}
}
-// AddField adds the value to the field with the given name. It returns an error if
-// the field is not defined in the schema, or if the type mismatched the field
-// type.
-func (m *SecuritySecretMutation) AddField(name string, value ent.Value) error {
- switch name {
- }
- return fmt.Errorf("unknown SecuritySecret numeric field %s", name)
+// OriginalPriceCleared returns if the "original_price" field was cleared in this mutation.
+func (m *SubscriptionPlanMutation) OriginalPriceCleared() bool {
+ _, ok := m.clearedFields[subscriptionplan.FieldOriginalPrice]
+ return ok
}
-// ClearedFields returns all nullable fields that were cleared during this
-// mutation.
-func (m *SecuritySecretMutation) ClearedFields() []string {
- return nil
+// ResetOriginalPrice resets all changes to the "original_price" field.
+func (m *SubscriptionPlanMutation) ResetOriginalPrice() {
+ m.original_price = nil
+ m.addoriginal_price = nil
+ delete(m.clearedFields, subscriptionplan.FieldOriginalPrice)
}
-// FieldCleared returns a boolean indicating if a field with the given name was
-// cleared in this mutation.
-func (m *SecuritySecretMutation) FieldCleared(name string) bool {
- _, ok := m.clearedFields[name]
- return ok
+// SetValidityDays sets the "validity_days" field.
+func (m *SubscriptionPlanMutation) SetValidityDays(i int) {
+ m.validity_days = &i
+ m.addvalidity_days = nil
}
-// ClearField clears the value of the field with the given name. It returns an
-// error if the field is not defined in the schema.
-func (m *SecuritySecretMutation) ClearField(name string) error {
- return fmt.Errorf("unknown SecuritySecret nullable field %s", name)
+// ValidityDays returns the value of the "validity_days" field in the mutation.
+func (m *SubscriptionPlanMutation) ValidityDays() (r int, exists bool) {
+ v := m.validity_days
+ if v == nil {
+ return
+ }
+ return *v, true
}
-// ResetField resets all changes in the mutation for the field with the given name.
-// It returns an error if the field is not defined in the schema.
-func (m *SecuritySecretMutation) ResetField(name string) error {
- switch name {
- case securitysecret.FieldCreatedAt:
- m.ResetCreatedAt()
- return nil
- case securitysecret.FieldUpdatedAt:
- m.ResetUpdatedAt()
- return nil
- case securitysecret.FieldKey:
- m.ResetKey()
- return nil
- case securitysecret.FieldValue:
- m.ResetValue()
- return nil
+// OldValidityDays returns the old "validity_days" field's value of the SubscriptionPlan entity.
+// If the SubscriptionPlan object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *SubscriptionPlanMutation) OldValidityDays(ctx context.Context) (v int, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldValidityDays is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldValidityDays requires an ID field in the mutation")
}
- return fmt.Errorf("unknown SecuritySecret field %s", name)
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldValidityDays: %w", err)
+ }
+ return oldValue.ValidityDays, nil
}
-// AddedEdges returns all edge names that were set/added in this mutation.
-func (m *SecuritySecretMutation) AddedEdges() []string {
- edges := make([]string, 0, 0)
- return edges
+// AddValidityDays adds i to the "validity_days" field.
+func (m *SubscriptionPlanMutation) AddValidityDays(i int) {
+ if m.addvalidity_days != nil {
+ *m.addvalidity_days += i
+ } else {
+ m.addvalidity_days = &i
+ }
}
-// AddedIDs returns all IDs (to other nodes) that were added for the given edge
-// name in this mutation.
-func (m *SecuritySecretMutation) AddedIDs(name string) []ent.Value {
- return nil
+// AddedValidityDays returns the value that was added to the "validity_days" field in this mutation.
+func (m *SubscriptionPlanMutation) AddedValidityDays() (r int, exists bool) {
+ v := m.addvalidity_days
+ if v == nil {
+ return
+ }
+ return *v, true
}
-// RemovedEdges returns all edge names that were removed in this mutation.
-func (m *SecuritySecretMutation) RemovedEdges() []string {
- edges := make([]string, 0, 0)
- return edges
+// ResetValidityDays resets all changes to the "validity_days" field.
+func (m *SubscriptionPlanMutation) ResetValidityDays() {
+ m.validity_days = nil
+ m.addvalidity_days = nil
}
-// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with
-// the given name in this mutation.
-func (m *SecuritySecretMutation) RemovedIDs(name string) []ent.Value {
- return nil
+// SetValidityUnit sets the "validity_unit" field.
+func (m *SubscriptionPlanMutation) SetValidityUnit(s string) {
+ m.validity_unit = &s
}
-// ClearedEdges returns all edge names that were cleared in this mutation.
-func (m *SecuritySecretMutation) ClearedEdges() []string {
- edges := make([]string, 0, 0)
- return edges
+// ValidityUnit returns the value of the "validity_unit" field in the mutation.
+func (m *SubscriptionPlanMutation) ValidityUnit() (r string, exists bool) {
+ v := m.validity_unit
+ if v == nil {
+ return
+ }
+ return *v, true
}
-// EdgeCleared returns a boolean which indicates if the edge with the given name
-// was cleared in this mutation.
-func (m *SecuritySecretMutation) EdgeCleared(name string) bool {
- return false
+// OldValidityUnit returns the old "validity_unit" field's value of the SubscriptionPlan entity.
+// If the SubscriptionPlan object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *SubscriptionPlanMutation) OldValidityUnit(ctx context.Context) (v string, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldValidityUnit is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldValidityUnit requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldValidityUnit: %w", err)
+ }
+ return oldValue.ValidityUnit, nil
}
-// ClearEdge clears the value of the edge with the given name. It returns an error
-// if that edge is not defined in the schema.
-func (m *SecuritySecretMutation) ClearEdge(name string) error {
- return fmt.Errorf("unknown SecuritySecret unique edge %s", name)
+// ResetValidityUnit resets all changes to the "validity_unit" field.
+func (m *SubscriptionPlanMutation) ResetValidityUnit() {
+ m.validity_unit = nil
}
-// ResetEdge resets all changes to the edge with the given name in this mutation.
-// It returns an error if the edge is not defined in the schema.
-func (m *SecuritySecretMutation) ResetEdge(name string) error {
- return fmt.Errorf("unknown SecuritySecret edge %s", name)
+// SetFeatures sets the "features" field.
+func (m *SubscriptionPlanMutation) SetFeatures(s string) {
+ m.features = &s
}
-// SettingMutation represents an operation that mutates the Setting nodes in the graph.
-type SettingMutation struct {
- config
- op Op
- typ string
- id *int64
- key *string
- value *string
- updated_at *time.Time
- clearedFields map[string]struct{}
- done bool
- oldValue func(context.Context) (*Setting, error)
- predicates []predicate.Setting
+// Features returns the value of the "features" field in the mutation.
+func (m *SubscriptionPlanMutation) Features() (r string, exists bool) {
+ v := m.features
+ if v == nil {
+ return
+ }
+ return *v, true
}
-var _ ent.Mutation = (*SettingMutation)(nil)
-
-// settingOption allows management of the mutation configuration using functional options.
-type settingOption func(*SettingMutation)
-
-// newSettingMutation creates new mutation for the Setting entity.
-func newSettingMutation(c config, op Op, opts ...settingOption) *SettingMutation {
- m := &SettingMutation{
- config: c,
- op: op,
- typ: TypeSetting,
- clearedFields: make(map[string]struct{}),
+// OldFeatures returns the old "features" field's value of the SubscriptionPlan entity.
+// If the SubscriptionPlan object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *SubscriptionPlanMutation) OldFeatures(ctx context.Context) (v string, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldFeatures is only allowed on UpdateOne operations")
}
- for _, opt := range opts {
- opt(m)
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldFeatures requires an ID field in the mutation")
}
- return m
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldFeatures: %w", err)
+ }
+ return oldValue.Features, nil
}
-// withSettingID sets the ID field of the mutation.
-func withSettingID(id int64) settingOption {
- return func(m *SettingMutation) {
- var (
- err error
- once sync.Once
- value *Setting
- )
- m.oldValue = func(ctx context.Context) (*Setting, error) {
- once.Do(func() {
- if m.done {
- err = errors.New("querying old values post mutation is not allowed")
- } else {
- value, err = m.Client().Setting.Get(ctx, id)
- }
- })
- return value, err
- }
- m.id = &id
+// ResetFeatures resets all changes to the "features" field.
+func (m *SubscriptionPlanMutation) ResetFeatures() {
+ m.features = nil
+}
+
+// SetProductName sets the "product_name" field.
+func (m *SubscriptionPlanMutation) SetProductName(s string) {
+ m.product_name = &s
+}
+
+// ProductName returns the value of the "product_name" field in the mutation.
+func (m *SubscriptionPlanMutation) ProductName() (r string, exists bool) {
+ v := m.product_name
+ if v == nil {
+ return
}
+ return *v, true
}
-// withSetting sets the old Setting of the mutation.
-func withSetting(node *Setting) settingOption {
- return func(m *SettingMutation) {
- m.oldValue = func(context.Context) (*Setting, error) {
- return node, nil
- }
- m.id = &node.ID
+// OldProductName returns the old "product_name" field's value of the SubscriptionPlan entity.
+// If the SubscriptionPlan object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *SubscriptionPlanMutation) OldProductName(ctx context.Context) (v string, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldProductName is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldProductName requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldProductName: %w", err)
}
+ return oldValue.ProductName, nil
}
-// Client returns a new `ent.Client` from the mutation. If the mutation was
-// executed in a transaction (ent.Tx), a transactional client is returned.
-func (m SettingMutation) Client() *Client {
- client := &Client{config: m.config}
- client.init()
- return client
+// ResetProductName resets all changes to the "product_name" field.
+func (m *SubscriptionPlanMutation) ResetProductName() {
+ m.product_name = nil
}
-// Tx returns an `ent.Tx` for mutations that were executed in transactions;
-// it returns an error otherwise.
-func (m SettingMutation) Tx() (*Tx, error) {
- if _, ok := m.driver.(*txDriver); !ok {
- return nil, errors.New("ent: mutation is not running in a transaction")
- }
- tx := &Tx{config: m.config}
- tx.init()
- return tx, nil
+// SetForSale sets the "for_sale" field.
+func (m *SubscriptionPlanMutation) SetForSale(b bool) {
+ m.for_sale = &b
}
-// ID returns the ID value in the mutation. Note that the ID is only available
-// if it was provided to the builder or after it was returned from the database.
-func (m *SettingMutation) ID() (id int64, exists bool) {
- if m.id == nil {
+// ForSale returns the value of the "for_sale" field in the mutation.
+func (m *SubscriptionPlanMutation) ForSale() (r bool, exists bool) {
+ v := m.for_sale
+ if v == nil {
return
}
- return *m.id, true
+ return *v, true
}
-// IDs queries the database and returns the entity ids that match the mutation's predicate.
-// That means, if the mutation is applied within a transaction with an isolation level such
-// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated
-// or updated by the mutation.
-func (m *SettingMutation) IDs(ctx context.Context) ([]int64, error) {
- switch {
- case m.op.Is(OpUpdateOne | OpDeleteOne):
- id, exists := m.ID()
- if exists {
- return []int64{id}, nil
- }
- fallthrough
- case m.op.Is(OpUpdate | OpDelete):
- return m.Client().Setting.Query().Where(m.predicates...).IDs(ctx)
- default:
- return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op)
+// OldForSale returns the old "for_sale" field's value of the SubscriptionPlan entity.
+// If the SubscriptionPlan object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *SubscriptionPlanMutation) OldForSale(ctx context.Context) (v bool, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldForSale is only allowed on UpdateOne operations")
}
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldForSale requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldForSale: %w", err)
+ }
+ return oldValue.ForSale, nil
}
-// SetKey sets the "key" field.
-func (m *SettingMutation) SetKey(s string) {
- m.key = &s
+// ResetForSale resets all changes to the "for_sale" field.
+func (m *SubscriptionPlanMutation) ResetForSale() {
+ m.for_sale = nil
}
-// Key returns the value of the "key" field in the mutation.
-func (m *SettingMutation) Key() (r string, exists bool) {
- v := m.key
+// SetSortOrder sets the "sort_order" field.
+func (m *SubscriptionPlanMutation) SetSortOrder(i int) {
+ m.sort_order = &i
+ m.addsort_order = nil
+}
+
+// SortOrder returns the value of the "sort_order" field in the mutation.
+func (m *SubscriptionPlanMutation) SortOrder() (r int, exists bool) {
+ v := m.sort_order
if v == nil {
return
}
return *v, true
}
-// OldKey returns the old "key" field's value of the Setting entity.
-// If the Setting object wasn't provided to the builder, the object is fetched from the database.
+// OldSortOrder returns the old "sort_order" field's value of the SubscriptionPlan entity.
+// If the SubscriptionPlan object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
-func (m *SettingMutation) OldKey(ctx context.Context) (v string, err error) {
+func (m *SubscriptionPlanMutation) OldSortOrder(ctx context.Context) (v int, err error) {
if !m.op.Is(OpUpdateOne) {
- return v, errors.New("OldKey is only allowed on UpdateOne operations")
+ return v, errors.New("OldSortOrder is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
- return v, errors.New("OldKey requires an ID field in the mutation")
+ return v, errors.New("OldSortOrder requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
- return v, fmt.Errorf("querying old value for OldKey: %w", err)
+ return v, fmt.Errorf("querying old value for OldSortOrder: %w", err)
}
- return oldValue.Key, nil
+ return oldValue.SortOrder, nil
}
-// ResetKey resets all changes to the "key" field.
-func (m *SettingMutation) ResetKey() {
- m.key = nil
+// AddSortOrder adds i to the "sort_order" field.
+func (m *SubscriptionPlanMutation) AddSortOrder(i int) {
+ if m.addsort_order != nil {
+ *m.addsort_order += i
+ } else {
+ m.addsort_order = &i
+ }
}
-// SetValue sets the "value" field.
-func (m *SettingMutation) SetValue(s string) {
- m.value = &s
+// AddedSortOrder returns the value that was added to the "sort_order" field in this mutation.
+func (m *SubscriptionPlanMutation) AddedSortOrder() (r int, exists bool) {
+ v := m.addsort_order
+ if v == nil {
+ return
+ }
+ return *v, true
}
-// Value returns the value of the "value" field in the mutation.
-func (m *SettingMutation) Value() (r string, exists bool) {
- v := m.value
+// ResetSortOrder resets all changes to the "sort_order" field.
+func (m *SubscriptionPlanMutation) ResetSortOrder() {
+ m.sort_order = nil
+ m.addsort_order = nil
+}
+
+// SetCreatedAt sets the "created_at" field.
+func (m *SubscriptionPlanMutation) SetCreatedAt(t time.Time) {
+ m.created_at = &t
+}
+
+// CreatedAt returns the value of the "created_at" field in the mutation.
+func (m *SubscriptionPlanMutation) CreatedAt() (r time.Time, exists bool) {
+ v := m.created_at
if v == nil {
return
}
return *v, true
}
-// OldValue returns the old "value" field's value of the Setting entity.
-// If the Setting object wasn't provided to the builder, the object is fetched from the database.
+// OldCreatedAt returns the old "created_at" field's value of the SubscriptionPlan entity.
+// If the SubscriptionPlan object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
-func (m *SettingMutation) OldValue(ctx context.Context) (v string, err error) {
+func (m *SubscriptionPlanMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) {
if !m.op.Is(OpUpdateOne) {
- return v, errors.New("OldValue is only allowed on UpdateOne operations")
+ return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
- return v, errors.New("OldValue requires an ID field in the mutation")
+ return v, errors.New("OldCreatedAt requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
- return v, fmt.Errorf("querying old value for OldValue: %w", err)
+ return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err)
}
- return oldValue.Value, nil
+ return oldValue.CreatedAt, nil
}
-// ResetValue resets all changes to the "value" field.
-func (m *SettingMutation) ResetValue() {
- m.value = nil
+// ResetCreatedAt resets all changes to the "created_at" field.
+func (m *SubscriptionPlanMutation) ResetCreatedAt() {
+ m.created_at = nil
}
// SetUpdatedAt sets the "updated_at" field.
-func (m *SettingMutation) SetUpdatedAt(t time.Time) {
+func (m *SubscriptionPlanMutation) SetUpdatedAt(t time.Time) {
m.updated_at = &t
}
// UpdatedAt returns the value of the "updated_at" field in the mutation.
-func (m *SettingMutation) UpdatedAt() (r time.Time, exists bool) {
+func (m *SubscriptionPlanMutation) UpdatedAt() (r time.Time, exists bool) {
v := m.updated_at
if v == nil {
return
@@ -16508,10 +21843,10 @@ func (m *SettingMutation) UpdatedAt() (r time.Time, exists bool) {
return *v, true
}
-// OldUpdatedAt returns the old "updated_at" field's value of the Setting entity.
-// If the Setting object wasn't provided to the builder, the object is fetched from the database.
+// OldUpdatedAt returns the old "updated_at" field's value of the SubscriptionPlan entity.
+// If the SubscriptionPlan object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
-func (m *SettingMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) {
+func (m *SubscriptionPlanMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations")
}
@@ -16526,19 +21861,19 @@ func (m *SettingMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err er
}
// ResetUpdatedAt resets all changes to the "updated_at" field.
-func (m *SettingMutation) ResetUpdatedAt() {
+func (m *SubscriptionPlanMutation) ResetUpdatedAt() {
m.updated_at = nil
}
-// Where appends a list predicates to the SettingMutation builder.
-func (m *SettingMutation) Where(ps ...predicate.Setting) {
+// Where appends a list predicates to the SubscriptionPlanMutation builder.
+func (m *SubscriptionPlanMutation) Where(ps ...predicate.SubscriptionPlan) {
m.predicates = append(m.predicates, ps...)
}
-// WhereP appends storage-level predicates to the SettingMutation builder. Using this method,
+// WhereP appends storage-level predicates to the SubscriptionPlanMutation builder. Using this method,
// users can use type-assertion to append predicates that do not depend on any generated package.
-func (m *SettingMutation) WhereP(ps ...func(*sql.Selector)) {
- p := make([]predicate.Setting, len(ps))
+func (m *SubscriptionPlanMutation) WhereP(ps ...func(*sql.Selector)) {
+ p := make([]predicate.SubscriptionPlan, len(ps))
for i := range ps {
p[i] = ps[i]
}
@@ -16546,33 +21881,63 @@ func (m *SettingMutation) WhereP(ps ...func(*sql.Selector)) {
}
// Op returns the operation name.
-func (m *SettingMutation) Op() Op {
+func (m *SubscriptionPlanMutation) Op() Op {
return m.op
}
// SetOp allows setting the mutation operation.
-func (m *SettingMutation) SetOp(op Op) {
+func (m *SubscriptionPlanMutation) SetOp(op Op) {
m.op = op
}
-// Type returns the node type of this mutation (Setting).
-func (m *SettingMutation) Type() string {
+// Type returns the node type of this mutation (SubscriptionPlan).
+func (m *SubscriptionPlanMutation) Type() string {
return m.typ
}
// Fields returns all fields that were changed during this mutation. Note that in
// order to get all numeric fields that were incremented/decremented, call
// AddedFields().
-func (m *SettingMutation) Fields() []string {
- fields := make([]string, 0, 3)
- if m.key != nil {
- fields = append(fields, setting.FieldKey)
+func (m *SubscriptionPlanMutation) Fields() []string {
+ fields := make([]string, 0, 13)
+ if m.group_id != nil {
+ fields = append(fields, subscriptionplan.FieldGroupID)
}
- if m.value != nil {
- fields = append(fields, setting.FieldValue)
+ if m.name != nil {
+ fields = append(fields, subscriptionplan.FieldName)
+ }
+ if m.description != nil {
+ fields = append(fields, subscriptionplan.FieldDescription)
+ }
+ if m.price != nil {
+ fields = append(fields, subscriptionplan.FieldPrice)
+ }
+ if m.original_price != nil {
+ fields = append(fields, subscriptionplan.FieldOriginalPrice)
+ }
+ if m.validity_days != nil {
+ fields = append(fields, subscriptionplan.FieldValidityDays)
+ }
+ if m.validity_unit != nil {
+ fields = append(fields, subscriptionplan.FieldValidityUnit)
+ }
+ if m.features != nil {
+ fields = append(fields, subscriptionplan.FieldFeatures)
+ }
+ if m.product_name != nil {
+ fields = append(fields, subscriptionplan.FieldProductName)
+ }
+ if m.for_sale != nil {
+ fields = append(fields, subscriptionplan.FieldForSale)
+ }
+ if m.sort_order != nil {
+ fields = append(fields, subscriptionplan.FieldSortOrder)
+ }
+ if m.created_at != nil {
+ fields = append(fields, subscriptionplan.FieldCreatedAt)
}
if m.updated_at != nil {
- fields = append(fields, setting.FieldUpdatedAt)
+ fields = append(fields, subscriptionplan.FieldUpdatedAt)
}
return fields
}
@@ -16580,13 +21945,33 @@ func (m *SettingMutation) Fields() []string {
// Field returns the value of a field with the given name. The second boolean
// return value indicates that this field was not set, or was not defined in the
// schema.
-func (m *SettingMutation) Field(name string) (ent.Value, bool) {
+func (m *SubscriptionPlanMutation) Field(name string) (ent.Value, bool) {
switch name {
- case setting.FieldKey:
- return m.Key()
- case setting.FieldValue:
- return m.Value()
- case setting.FieldUpdatedAt:
+ case subscriptionplan.FieldGroupID:
+ return m.GroupID()
+ case subscriptionplan.FieldName:
+ return m.Name()
+ case subscriptionplan.FieldDescription:
+ return m.Description()
+ case subscriptionplan.FieldPrice:
+ return m.Price()
+ case subscriptionplan.FieldOriginalPrice:
+ return m.OriginalPrice()
+ case subscriptionplan.FieldValidityDays:
+ return m.ValidityDays()
+ case subscriptionplan.FieldValidityUnit:
+ return m.ValidityUnit()
+ case subscriptionplan.FieldFeatures:
+ return m.Features()
+ case subscriptionplan.FieldProductName:
+ return m.ProductName()
+ case subscriptionplan.FieldForSale:
+ return m.ForSale()
+ case subscriptionplan.FieldSortOrder:
+ return m.SortOrder()
+ case subscriptionplan.FieldCreatedAt:
+ return m.CreatedAt()
+ case subscriptionplan.FieldUpdatedAt:
return m.UpdatedAt()
}
return nil, false
@@ -16595,38 +21980,128 @@ func (m *SettingMutation) Field(name string) (ent.Value, bool) {
// OldField returns the old value of the field from the database. An error is
// returned if the mutation operation is not UpdateOne, or the query to the
// database failed.
-func (m *SettingMutation) OldField(ctx context.Context, name string) (ent.Value, error) {
+func (m *SubscriptionPlanMutation) OldField(ctx context.Context, name string) (ent.Value, error) {
switch name {
- case setting.FieldKey:
- return m.OldKey(ctx)
- case setting.FieldValue:
- return m.OldValue(ctx)
- case setting.FieldUpdatedAt:
+ case subscriptionplan.FieldGroupID:
+ return m.OldGroupID(ctx)
+ case subscriptionplan.FieldName:
+ return m.OldName(ctx)
+ case subscriptionplan.FieldDescription:
+ return m.OldDescription(ctx)
+ case subscriptionplan.FieldPrice:
+ return m.OldPrice(ctx)
+ case subscriptionplan.FieldOriginalPrice:
+ return m.OldOriginalPrice(ctx)
+ case subscriptionplan.FieldValidityDays:
+ return m.OldValidityDays(ctx)
+ case subscriptionplan.FieldValidityUnit:
+ return m.OldValidityUnit(ctx)
+ case subscriptionplan.FieldFeatures:
+ return m.OldFeatures(ctx)
+ case subscriptionplan.FieldProductName:
+ return m.OldProductName(ctx)
+ case subscriptionplan.FieldForSale:
+ return m.OldForSale(ctx)
+ case subscriptionplan.FieldSortOrder:
+ return m.OldSortOrder(ctx)
+ case subscriptionplan.FieldCreatedAt:
+ return m.OldCreatedAt(ctx)
+ case subscriptionplan.FieldUpdatedAt:
return m.OldUpdatedAt(ctx)
}
- return nil, fmt.Errorf("unknown Setting field %s", name)
+ return nil, fmt.Errorf("unknown SubscriptionPlan field %s", name)
}
// SetField sets the value of a field with the given name. It returns an error if
// the field is not defined in the schema, or if the type mismatched the field
// type.
-func (m *SettingMutation) SetField(name string, value ent.Value) error {
+func (m *SubscriptionPlanMutation) SetField(name string, value ent.Value) error {
switch name {
- case setting.FieldKey:
+ case subscriptionplan.FieldGroupID:
+ v, ok := value.(int64)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetGroupID(v)
+ return nil
+ case subscriptionplan.FieldName:
v, ok := value.(string)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
- m.SetKey(v)
+ m.SetName(v)
return nil
- case setting.FieldValue:
+ case subscriptionplan.FieldDescription:
v, ok := value.(string)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
- m.SetValue(v)
+ m.SetDescription(v)
return nil
- case setting.FieldUpdatedAt:
+ case subscriptionplan.FieldPrice:
+ v, ok := value.(float64)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetPrice(v)
+ return nil
+ case subscriptionplan.FieldOriginalPrice:
+ v, ok := value.(float64)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetOriginalPrice(v)
+ return nil
+ case subscriptionplan.FieldValidityDays:
+ v, ok := value.(int)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetValidityDays(v)
+ return nil
+ case subscriptionplan.FieldValidityUnit:
+ v, ok := value.(string)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetValidityUnit(v)
+ return nil
+ case subscriptionplan.FieldFeatures:
+ v, ok := value.(string)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetFeatures(v)
+ return nil
+ case subscriptionplan.FieldProductName:
+ v, ok := value.(string)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetProductName(v)
+ return nil
+ case subscriptionplan.FieldForSale:
+ v, ok := value.(bool)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetForSale(v)
+ return nil
+ case subscriptionplan.FieldSortOrder:
+ v, ok := value.(int)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetSortOrder(v)
+ return nil
+ case subscriptionplan.FieldCreatedAt:
+ v, ok := value.(time.Time)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetCreatedAt(v)
+ return nil
+ case subscriptionplan.FieldUpdatedAt:
v, ok := value.(time.Time)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
@@ -16634,113 +22109,215 @@ func (m *SettingMutation) SetField(name string, value ent.Value) error {
m.SetUpdatedAt(v)
return nil
}
- return fmt.Errorf("unknown Setting field %s", name)
+ return fmt.Errorf("unknown SubscriptionPlan field %s", name)
}
// AddedFields returns all numeric fields that were incremented/decremented during
// this mutation.
-func (m *SettingMutation) AddedFields() []string {
- return nil
+func (m *SubscriptionPlanMutation) AddedFields() []string {
+ var fields []string
+ if m.addgroup_id != nil {
+ fields = append(fields, subscriptionplan.FieldGroupID)
+ }
+ if m.addprice != nil {
+ fields = append(fields, subscriptionplan.FieldPrice)
+ }
+ if m.addoriginal_price != nil {
+ fields = append(fields, subscriptionplan.FieldOriginalPrice)
+ }
+ if m.addvalidity_days != nil {
+ fields = append(fields, subscriptionplan.FieldValidityDays)
+ }
+ if m.addsort_order != nil {
+ fields = append(fields, subscriptionplan.FieldSortOrder)
+ }
+ return fields
}
// AddedField returns the numeric value that was incremented/decremented on a field
// with the given name. The second boolean return value indicates that this field
// was not set, or was not defined in the schema.
-func (m *SettingMutation) AddedField(name string) (ent.Value, bool) {
+func (m *SubscriptionPlanMutation) AddedField(name string) (ent.Value, bool) {
+ switch name {
+ case subscriptionplan.FieldGroupID:
+ return m.AddedGroupID()
+ case subscriptionplan.FieldPrice:
+ return m.AddedPrice()
+ case subscriptionplan.FieldOriginalPrice:
+ return m.AddedOriginalPrice()
+ case subscriptionplan.FieldValidityDays:
+ return m.AddedValidityDays()
+ case subscriptionplan.FieldSortOrder:
+ return m.AddedSortOrder()
+ }
return nil, false
}
// AddField adds the value to the field with the given name. It returns an error if
// the field is not defined in the schema, or if the type mismatched the field
// type.
-func (m *SettingMutation) AddField(name string, value ent.Value) error {
+func (m *SubscriptionPlanMutation) AddField(name string, value ent.Value) error {
switch name {
+ case subscriptionplan.FieldGroupID:
+ v, ok := value.(int64)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.AddGroupID(v)
+ return nil
+ case subscriptionplan.FieldPrice:
+ v, ok := value.(float64)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.AddPrice(v)
+ return nil
+ case subscriptionplan.FieldOriginalPrice:
+ v, ok := value.(float64)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.AddOriginalPrice(v)
+ return nil
+ case subscriptionplan.FieldValidityDays:
+ v, ok := value.(int)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.AddValidityDays(v)
+ return nil
+ case subscriptionplan.FieldSortOrder:
+ v, ok := value.(int)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.AddSortOrder(v)
+ return nil
}
- return fmt.Errorf("unknown Setting numeric field %s", name)
+ return fmt.Errorf("unknown SubscriptionPlan numeric field %s", name)
}
// ClearedFields returns all nullable fields that were cleared during this
// mutation.
-func (m *SettingMutation) ClearedFields() []string {
- return nil
+func (m *SubscriptionPlanMutation) ClearedFields() []string {
+ var fields []string
+ if m.FieldCleared(subscriptionplan.FieldOriginalPrice) {
+ fields = append(fields, subscriptionplan.FieldOriginalPrice)
+ }
+ return fields
}
// FieldCleared returns a boolean indicating if a field with the given name was
// cleared in this mutation.
-func (m *SettingMutation) FieldCleared(name string) bool {
+func (m *SubscriptionPlanMutation) FieldCleared(name string) bool {
_, ok := m.clearedFields[name]
return ok
}
// ClearField clears the value of the field with the given name. It returns an
// error if the field is not defined in the schema.
-func (m *SettingMutation) ClearField(name string) error {
- return fmt.Errorf("unknown Setting nullable field %s", name)
+func (m *SubscriptionPlanMutation) ClearField(name string) error {
+ switch name {
+ case subscriptionplan.FieldOriginalPrice:
+ m.ClearOriginalPrice()
+ return nil
+ }
+ return fmt.Errorf("unknown SubscriptionPlan nullable field %s", name)
}
// ResetField resets all changes in the mutation for the field with the given name.
// It returns an error if the field is not defined in the schema.
-func (m *SettingMutation) ResetField(name string) error {
+func (m *SubscriptionPlanMutation) ResetField(name string) error {
switch name {
- case setting.FieldKey:
- m.ResetKey()
+ case subscriptionplan.FieldGroupID:
+ m.ResetGroupID()
return nil
- case setting.FieldValue:
- m.ResetValue()
+ case subscriptionplan.FieldName:
+ m.ResetName()
return nil
- case setting.FieldUpdatedAt:
+ case subscriptionplan.FieldDescription:
+ m.ResetDescription()
+ return nil
+ case subscriptionplan.FieldPrice:
+ m.ResetPrice()
+ return nil
+ case subscriptionplan.FieldOriginalPrice:
+ m.ResetOriginalPrice()
+ return nil
+ case subscriptionplan.FieldValidityDays:
+ m.ResetValidityDays()
+ return nil
+ case subscriptionplan.FieldValidityUnit:
+ m.ResetValidityUnit()
+ return nil
+ case subscriptionplan.FieldFeatures:
+ m.ResetFeatures()
+ return nil
+ case subscriptionplan.FieldProductName:
+ m.ResetProductName()
+ return nil
+ case subscriptionplan.FieldForSale:
+ m.ResetForSale()
+ return nil
+ case subscriptionplan.FieldSortOrder:
+ m.ResetSortOrder()
+ return nil
+ case subscriptionplan.FieldCreatedAt:
+ m.ResetCreatedAt()
+ return nil
+ case subscriptionplan.FieldUpdatedAt:
m.ResetUpdatedAt()
return nil
}
- return fmt.Errorf("unknown Setting field %s", name)
+ return fmt.Errorf("unknown SubscriptionPlan field %s", name)
}
// AddedEdges returns all edge names that were set/added in this mutation.
-func (m *SettingMutation) AddedEdges() []string {
+func (m *SubscriptionPlanMutation) AddedEdges() []string {
edges := make([]string, 0, 0)
return edges
}
// AddedIDs returns all IDs (to other nodes) that were added for the given edge
// name in this mutation.
-func (m *SettingMutation) AddedIDs(name string) []ent.Value {
+func (m *SubscriptionPlanMutation) AddedIDs(name string) []ent.Value {
return nil
}
// RemovedEdges returns all edge names that were removed in this mutation.
-func (m *SettingMutation) RemovedEdges() []string {
+func (m *SubscriptionPlanMutation) RemovedEdges() []string {
edges := make([]string, 0, 0)
return edges
}
// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with
// the given name in this mutation.
-func (m *SettingMutation) RemovedIDs(name string) []ent.Value {
+func (m *SubscriptionPlanMutation) RemovedIDs(name string) []ent.Value {
return nil
}
// ClearedEdges returns all edge names that were cleared in this mutation.
-func (m *SettingMutation) ClearedEdges() []string {
+func (m *SubscriptionPlanMutation) ClearedEdges() []string {
edges := make([]string, 0, 0)
return edges
}
// EdgeCleared returns a boolean which indicates if the edge with the given name
// was cleared in this mutation.
-func (m *SettingMutation) EdgeCleared(name string) bool {
+func (m *SubscriptionPlanMutation) EdgeCleared(name string) bool {
return false
}
// ClearEdge clears the value of the edge with the given name. It returns an error
// if that edge is not defined in the schema.
-func (m *SettingMutation) ClearEdge(name string) error {
- return fmt.Errorf("unknown Setting unique edge %s", name)
+func (m *SubscriptionPlanMutation) ClearEdge(name string) error {
+ return fmt.Errorf("unknown SubscriptionPlan unique edge %s", name)
}
// ResetEdge resets all changes to the edge with the given name in this mutation.
// It returns an error if the edge is not defined in the schema.
-func (m *SettingMutation) ResetEdge(name string) error {
- return fmt.Errorf("unknown Setting edge %s", name)
+func (m *SubscriptionPlanMutation) ResetEdge(name string) error {
+ return fmt.Errorf("unknown SubscriptionPlan edge %s", name)
}
// TLSFingerprintProfileMutation represents an operation that mutates the TLSFingerprintProfile nodes in the graph.
@@ -22661,6 +28238,9 @@ type UserMutation struct {
promo_code_usages map[int64]struct{}
removedpromo_code_usages map[int64]struct{}
clearedpromo_code_usages bool
+ payment_orders map[int64]struct{}
+ removedpayment_orders map[int64]struct{}
+ clearedpayment_orders bool
done bool
oldValue func(context.Context) (*User, error)
predicates []predicate.User
@@ -23833,6 +29413,60 @@ func (m *UserMutation) ResetPromoCodeUsages() {
m.removedpromo_code_usages = nil
}
+// AddPaymentOrderIDs adds the "payment_orders" edge to the PaymentOrder entity by ids.
+func (m *UserMutation) AddPaymentOrderIDs(ids ...int64) {
+ if m.payment_orders == nil {
+ m.payment_orders = make(map[int64]struct{})
+ }
+ for i := range ids {
+ m.payment_orders[ids[i]] = struct{}{}
+ }
+}
+
+// ClearPaymentOrders clears the "payment_orders" edge to the PaymentOrder entity.
+func (m *UserMutation) ClearPaymentOrders() {
+ m.clearedpayment_orders = true
+}
+
+// PaymentOrdersCleared reports if the "payment_orders" edge to the PaymentOrder entity was cleared.
+func (m *UserMutation) PaymentOrdersCleared() bool {
+ return m.clearedpayment_orders
+}
+
+// RemovePaymentOrderIDs removes the "payment_orders" edge to the PaymentOrder entity by IDs.
+func (m *UserMutation) RemovePaymentOrderIDs(ids ...int64) {
+ if m.removedpayment_orders == nil {
+ m.removedpayment_orders = make(map[int64]struct{})
+ }
+ for i := range ids {
+ delete(m.payment_orders, ids[i])
+ m.removedpayment_orders[ids[i]] = struct{}{}
+ }
+}
+
+// RemovedPaymentOrders returns the removed IDs of the "payment_orders" edge to the PaymentOrder entity.
+func (m *UserMutation) RemovedPaymentOrdersIDs() (ids []int64) {
+ for id := range m.removedpayment_orders {
+ ids = append(ids, id)
+ }
+ return
+}
+
+// PaymentOrdersIDs returns the "payment_orders" edge IDs in the mutation.
+func (m *UserMutation) PaymentOrdersIDs() (ids []int64) {
+ for id := range m.payment_orders {
+ ids = append(ids, id)
+ }
+ return
+}
+
+// ResetPaymentOrders resets all changes to the "payment_orders" edge.
+func (m *UserMutation) ResetPaymentOrders() {
+ m.payment_orders = nil
+ m.clearedpayment_orders = false
+ m.removedpayment_orders = nil
+}
+
// Where appends a list predicates to the UserMutation builder.
func (m *UserMutation) Where(ps ...predicate.User) {
m.predicates = append(m.predicates, ps...)
@@ -24235,7 +29869,7 @@ func (m *UserMutation) ResetField(name string) error {
// AddedEdges returns all edge names that were set/added in this mutation.
func (m *UserMutation) AddedEdges() []string {
- edges := make([]string, 0, 9)
+ edges := make([]string, 0, 10)
if m.api_keys != nil {
edges = append(edges, user.EdgeAPIKeys)
}
@@ -24263,6 +29897,9 @@ func (m *UserMutation) AddedEdges() []string {
if m.promo_code_usages != nil {
edges = append(edges, user.EdgePromoCodeUsages)
}
+ if m.payment_orders != nil {
+ edges = append(edges, user.EdgePaymentOrders)
+ }
return edges
}
@@ -24324,13 +29961,19 @@ func (m *UserMutation) AddedIDs(name string) []ent.Value {
ids = append(ids, id)
}
return ids
+ case user.EdgePaymentOrders:
+ ids := make([]ent.Value, 0, len(m.payment_orders))
+ for id := range m.payment_orders {
+ ids = append(ids, id)
+ }
+ return ids
}
return nil
}
// RemovedEdges returns all edge names that were removed in this mutation.
func (m *UserMutation) RemovedEdges() []string {
- edges := make([]string, 0, 9)
+ edges := make([]string, 0, 10)
if m.removedapi_keys != nil {
edges = append(edges, user.EdgeAPIKeys)
}
@@ -24358,6 +30001,9 @@ func (m *UserMutation) RemovedEdges() []string {
if m.removedpromo_code_usages != nil {
edges = append(edges, user.EdgePromoCodeUsages)
}
+ if m.removedpayment_orders != nil {
+ edges = append(edges, user.EdgePaymentOrders)
+ }
return edges
}
@@ -24419,13 +30065,19 @@ func (m *UserMutation) RemovedIDs(name string) []ent.Value {
ids = append(ids, id)
}
return ids
+ case user.EdgePaymentOrders:
+ ids := make([]ent.Value, 0, len(m.removedpayment_orders))
+ for id := range m.removedpayment_orders {
+ ids = append(ids, id)
+ }
+ return ids
}
return nil
}
// ClearedEdges returns all edge names that were cleared in this mutation.
func (m *UserMutation) ClearedEdges() []string {
- edges := make([]string, 0, 9)
+ edges := make([]string, 0, 10)
if m.clearedapi_keys {
edges = append(edges, user.EdgeAPIKeys)
}
@@ -24453,6 +30105,9 @@ func (m *UserMutation) ClearedEdges() []string {
if m.clearedpromo_code_usages {
edges = append(edges, user.EdgePromoCodeUsages)
}
+ if m.clearedpayment_orders {
+ edges = append(edges, user.EdgePaymentOrders)
+ }
return edges
}
@@ -24478,6 +30133,8 @@ func (m *UserMutation) EdgeCleared(name string) bool {
return m.clearedattribute_values
case user.EdgePromoCodeUsages:
return m.clearedpromo_code_usages
+ case user.EdgePaymentOrders:
+ return m.clearedpayment_orders
}
return false
}
@@ -24521,6 +30178,9 @@ func (m *UserMutation) ResetEdge(name string) error {
case user.EdgePromoCodeUsages:
m.ResetPromoCodeUsages()
return nil
+ case user.EdgePaymentOrders:
+ m.ResetPaymentOrders()
+ return nil
}
return fmt.Errorf("unknown User edge %s", name)
}
diff --git a/backend/ent/paymentauditlog.go b/backend/ent/paymentauditlog.go
new file mode 100644
index 0000000000000000000000000000000000000000..ffcdcfcd95562d08ea78e132c467f3f98b60402f
--- /dev/null
+++ b/backend/ent/paymentauditlog.go
@@ -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
diff --git a/backend/ent/paymentauditlog/paymentauditlog.go b/backend/ent/paymentauditlog/paymentauditlog.go
new file mode 100644
index 0000000000000000000000000000000000000000..9d480eef21971edb19687fbcc671cf75bc90c624
--- /dev/null
+++ b/backend/ent/paymentauditlog/paymentauditlog.go
@@ -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()
+}
diff --git a/backend/ent/paymentauditlog/where.go b/backend/ent/paymentauditlog/where.go
new file mode 100644
index 0000000000000000000000000000000000000000..2fd80a42130d305977fb344d712d74889abd27a2
--- /dev/null
+++ b/backend/ent/paymentauditlog/where.go
@@ -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))
+}
diff --git a/backend/ent/paymentauditlog_create.go b/backend/ent/paymentauditlog_create.go
new file mode 100644
index 0000000000000000000000000000000000000000..1906aba110ba67ba068fa25e1e0b2c042572d1f8
--- /dev/null
+++ b/backend/ent/paymentauditlog_create.go
@@ -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)
+ }
+}
diff --git a/backend/ent/paymentauditlog_delete.go b/backend/ent/paymentauditlog_delete.go
new file mode 100644
index 0000000000000000000000000000000000000000..ca22d8db37e450e7ccd510a4c0026b17c401dd8f
--- /dev/null
+++ b/backend/ent/paymentauditlog_delete.go
@@ -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)
+ }
+}
diff --git a/backend/ent/paymentauditlog_query.go b/backend/ent/paymentauditlog_query.go
new file mode 100644
index 0000000000000000000000000000000000000000..7a4e9115e7f5820ae81f26ea9838964b8115f20d
--- /dev/null
+++ b/backend/ent/paymentauditlog_query.go
@@ -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)
+}
diff --git a/backend/ent/paymentauditlog_update.go b/backend/ent/paymentauditlog_update.go
new file mode 100644
index 0000000000000000000000000000000000000000..52b4afe794437008afc840096a7cc6080f485a90
--- /dev/null
+++ b/backend/ent/paymentauditlog_update.go
@@ -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
+}
diff --git a/backend/ent/paymentorder.go b/backend/ent/paymentorder.go
new file mode 100644
index 0000000000000000000000000000000000000000..6ea3e70981d1884751b7512e541f53c057c1c206
--- /dev/null
+++ b/backend/ent/paymentorder.go
@@ -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
diff --git a/backend/ent/paymentorder/paymentorder.go b/backend/ent/paymentorder/paymentorder.go
new file mode 100644
index 0000000000000000000000000000000000000000..4467b2b635896402c3254245eff2fec0d8fb4136
--- /dev/null
+++ b/backend/ent/paymentorder/paymentorder.go
@@ -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),
+ )
+}
diff --git a/backend/ent/paymentorder/where.go b/backend/ent/paymentorder/where.go
new file mode 100644
index 0000000000000000000000000000000000000000..78520fac4286fd7281262789aa3154697f1e3951
--- /dev/null
+++ b/backend/ent/paymentorder/where.go
@@ -0,0 +1,2389 @@
+// Code generated by ent, DO NOT EDIT.
+
+package paymentorder
+
+import (
+ "time"
+
+ "entgo.io/ent/dialect/sql"
+ "entgo.io/ent/dialect/sql/sqlgraph"
+ "github.com/Wei-Shaw/sub2api/ent/predicate"
+)
+
+// ID filters vertices based on their ID field.
+func ID(id int64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldID, id))
+}
+
+// IDEQ applies the EQ predicate on the ID field.
+func IDEQ(id int64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldID, id))
+}
+
+// IDNEQ applies the NEQ predicate on the ID field.
+func IDNEQ(id int64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNEQ(FieldID, id))
+}
+
+// IDIn applies the In predicate on the ID field.
+func IDIn(ids ...int64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIn(FieldID, ids...))
+}
+
+// IDNotIn applies the NotIn predicate on the ID field.
+func IDNotIn(ids ...int64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotIn(FieldID, ids...))
+}
+
+// IDGT applies the GT predicate on the ID field.
+func IDGT(id int64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGT(FieldID, id))
+}
+
+// IDGTE applies the GTE predicate on the ID field.
+func IDGTE(id int64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGTE(FieldID, id))
+}
+
+// IDLT applies the LT predicate on the ID field.
+func IDLT(id int64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLT(FieldID, id))
+}
+
+// IDLTE applies the LTE predicate on the ID field.
+func IDLTE(id int64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLTE(FieldID, id))
+}
+
+// UserID applies equality check predicate on the "user_id" field. It's identical to UserIDEQ.
+func UserID(v int64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldUserID, v))
+}
+
+// UserEmail applies equality check predicate on the "user_email" field. It's identical to UserEmailEQ.
+func UserEmail(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldUserEmail, v))
+}
+
+// UserName applies equality check predicate on the "user_name" field. It's identical to UserNameEQ.
+func UserName(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldUserName, v))
+}
+
+// UserNotes applies equality check predicate on the "user_notes" field. It's identical to UserNotesEQ.
+func UserNotes(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldUserNotes, v))
+}
+
+// Amount applies equality check predicate on the "amount" field. It's identical to AmountEQ.
+func Amount(v float64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldAmount, v))
+}
+
+// PayAmount applies equality check predicate on the "pay_amount" field. It's identical to PayAmountEQ.
+func PayAmount(v float64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldPayAmount, v))
+}
+
+// FeeRate applies equality check predicate on the "fee_rate" field. It's identical to FeeRateEQ.
+func FeeRate(v float64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldFeeRate, v))
+}
+
+// RechargeCode applies equality check predicate on the "recharge_code" field. It's identical to RechargeCodeEQ.
+func RechargeCode(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldRechargeCode, v))
+}
+
+// OutTradeNo applies equality check predicate on the "out_trade_no" field. It's identical to OutTradeNoEQ.
+func OutTradeNo(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldOutTradeNo, v))
+}
+
+// PaymentType applies equality check predicate on the "payment_type" field. It's identical to PaymentTypeEQ.
+func PaymentType(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldPaymentType, v))
+}
+
+// PaymentTradeNo applies equality check predicate on the "payment_trade_no" field. It's identical to PaymentTradeNoEQ.
+func PaymentTradeNo(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldPaymentTradeNo, v))
+}
+
+// PayURL applies equality check predicate on the "pay_url" field. It's identical to PayURLEQ.
+func PayURL(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldPayURL, v))
+}
+
+// QrCode applies equality check predicate on the "qr_code" field. It's identical to QrCodeEQ.
+func QrCode(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldQrCode, v))
+}
+
+// QrCodeImg applies equality check predicate on the "qr_code_img" field. It's identical to QrCodeImgEQ.
+func QrCodeImg(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldQrCodeImg, v))
+}
+
+// OrderType applies equality check predicate on the "order_type" field. It's identical to OrderTypeEQ.
+func OrderType(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldOrderType, v))
+}
+
+// PlanID applies equality check predicate on the "plan_id" field. It's identical to PlanIDEQ.
+func PlanID(v int64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldPlanID, v))
+}
+
+// SubscriptionGroupID applies equality check predicate on the "subscription_group_id" field. It's identical to SubscriptionGroupIDEQ.
+func SubscriptionGroupID(v int64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldSubscriptionGroupID, v))
+}
+
+// SubscriptionDays applies equality check predicate on the "subscription_days" field. It's identical to SubscriptionDaysEQ.
+func SubscriptionDays(v int) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldSubscriptionDays, v))
+}
+
+// ProviderInstanceID applies equality check predicate on the "provider_instance_id" field. It's identical to ProviderInstanceIDEQ.
+func ProviderInstanceID(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldProviderInstanceID, v))
+}
+
+// Status applies equality check predicate on the "status" field. It's identical to StatusEQ.
+func Status(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldStatus, v))
+}
+
+// RefundAmount applies equality check predicate on the "refund_amount" field. It's identical to RefundAmountEQ.
+func RefundAmount(v float64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldRefundAmount, v))
+}
+
+// RefundReason applies equality check predicate on the "refund_reason" field. It's identical to RefundReasonEQ.
+func RefundReason(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldRefundReason, v))
+}
+
+// RefundAt applies equality check predicate on the "refund_at" field. It's identical to RefundAtEQ.
+func RefundAt(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldRefundAt, v))
+}
+
+// ForceRefund applies equality check predicate on the "force_refund" field. It's identical to ForceRefundEQ.
+func ForceRefund(v bool) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldForceRefund, v))
+}
+
+// RefundRequestedAt applies equality check predicate on the "refund_requested_at" field. It's identical to RefundRequestedAtEQ.
+func RefundRequestedAt(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldRefundRequestedAt, v))
+}
+
+// RefundRequestReason applies equality check predicate on the "refund_request_reason" field. It's identical to RefundRequestReasonEQ.
+func RefundRequestReason(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldRefundRequestReason, v))
+}
+
+// RefundRequestedBy applies equality check predicate on the "refund_requested_by" field. It's identical to RefundRequestedByEQ.
+func RefundRequestedBy(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldRefundRequestedBy, v))
+}
+
+// ExpiresAt applies equality check predicate on the "expires_at" field. It's identical to ExpiresAtEQ.
+func ExpiresAt(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldExpiresAt, v))
+}
+
+// PaidAt applies equality check predicate on the "paid_at" field. It's identical to PaidAtEQ.
+func PaidAt(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldPaidAt, v))
+}
+
+// CompletedAt applies equality check predicate on the "completed_at" field. It's identical to CompletedAtEQ.
+func CompletedAt(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldCompletedAt, v))
+}
+
+// FailedAt applies equality check predicate on the "failed_at" field. It's identical to FailedAtEQ.
+func FailedAt(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldFailedAt, v))
+}
+
+// FailedReason applies equality check predicate on the "failed_reason" field. It's identical to FailedReasonEQ.
+func FailedReason(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldFailedReason, v))
+}
+
+// ClientIP applies equality check predicate on the "client_ip" field. It's identical to ClientIPEQ.
+func ClientIP(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldClientIP, v))
+}
+
+// SrcHost applies equality check predicate on the "src_host" field. It's identical to SrcHostEQ.
+func SrcHost(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldSrcHost, v))
+}
+
+// SrcURL applies equality check predicate on the "src_url" field. It's identical to SrcURLEQ.
+func SrcURL(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldSrcURL, v))
+}
+
+// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ.
+func CreatedAt(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(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.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldUpdatedAt, v))
+}
+
+// UserIDEQ applies the EQ predicate on the "user_id" field.
+func UserIDEQ(v int64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldUserID, v))
+}
+
+// UserIDNEQ applies the NEQ predicate on the "user_id" field.
+func UserIDNEQ(v int64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNEQ(FieldUserID, v))
+}
+
+// UserIDIn applies the In predicate on the "user_id" field.
+func UserIDIn(vs ...int64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIn(FieldUserID, vs...))
+}
+
+// UserIDNotIn applies the NotIn predicate on the "user_id" field.
+func UserIDNotIn(vs ...int64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotIn(FieldUserID, vs...))
+}
+
+// UserEmailEQ applies the EQ predicate on the "user_email" field.
+func UserEmailEQ(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldUserEmail, v))
+}
+
+// UserEmailNEQ applies the NEQ predicate on the "user_email" field.
+func UserEmailNEQ(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNEQ(FieldUserEmail, v))
+}
+
+// UserEmailIn applies the In predicate on the "user_email" field.
+func UserEmailIn(vs ...string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIn(FieldUserEmail, vs...))
+}
+
+// UserEmailNotIn applies the NotIn predicate on the "user_email" field.
+func UserEmailNotIn(vs ...string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotIn(FieldUserEmail, vs...))
+}
+
+// UserEmailGT applies the GT predicate on the "user_email" field.
+func UserEmailGT(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGT(FieldUserEmail, v))
+}
+
+// UserEmailGTE applies the GTE predicate on the "user_email" field.
+func UserEmailGTE(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGTE(FieldUserEmail, v))
+}
+
+// UserEmailLT applies the LT predicate on the "user_email" field.
+func UserEmailLT(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLT(FieldUserEmail, v))
+}
+
+// UserEmailLTE applies the LTE predicate on the "user_email" field.
+func UserEmailLTE(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLTE(FieldUserEmail, v))
+}
+
+// UserEmailContains applies the Contains predicate on the "user_email" field.
+func UserEmailContains(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldContains(FieldUserEmail, v))
+}
+
+// UserEmailHasPrefix applies the HasPrefix predicate on the "user_email" field.
+func UserEmailHasPrefix(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldHasPrefix(FieldUserEmail, v))
+}
+
+// UserEmailHasSuffix applies the HasSuffix predicate on the "user_email" field.
+func UserEmailHasSuffix(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldHasSuffix(FieldUserEmail, v))
+}
+
+// UserEmailEqualFold applies the EqualFold predicate on the "user_email" field.
+func UserEmailEqualFold(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEqualFold(FieldUserEmail, v))
+}
+
+// UserEmailContainsFold applies the ContainsFold predicate on the "user_email" field.
+func UserEmailContainsFold(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldContainsFold(FieldUserEmail, v))
+}
+
+// UserNameEQ applies the EQ predicate on the "user_name" field.
+func UserNameEQ(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldUserName, v))
+}
+
+// UserNameNEQ applies the NEQ predicate on the "user_name" field.
+func UserNameNEQ(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNEQ(FieldUserName, v))
+}
+
+// UserNameIn applies the In predicate on the "user_name" field.
+func UserNameIn(vs ...string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIn(FieldUserName, vs...))
+}
+
+// UserNameNotIn applies the NotIn predicate on the "user_name" field.
+func UserNameNotIn(vs ...string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotIn(FieldUserName, vs...))
+}
+
+// UserNameGT applies the GT predicate on the "user_name" field.
+func UserNameGT(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGT(FieldUserName, v))
+}
+
+// UserNameGTE applies the GTE predicate on the "user_name" field.
+func UserNameGTE(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGTE(FieldUserName, v))
+}
+
+// UserNameLT applies the LT predicate on the "user_name" field.
+func UserNameLT(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLT(FieldUserName, v))
+}
+
+// UserNameLTE applies the LTE predicate on the "user_name" field.
+func UserNameLTE(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLTE(FieldUserName, v))
+}
+
+// UserNameContains applies the Contains predicate on the "user_name" field.
+func UserNameContains(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldContains(FieldUserName, v))
+}
+
+// UserNameHasPrefix applies the HasPrefix predicate on the "user_name" field.
+func UserNameHasPrefix(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldHasPrefix(FieldUserName, v))
+}
+
+// UserNameHasSuffix applies the HasSuffix predicate on the "user_name" field.
+func UserNameHasSuffix(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldHasSuffix(FieldUserName, v))
+}
+
+// UserNameEqualFold applies the EqualFold predicate on the "user_name" field.
+func UserNameEqualFold(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEqualFold(FieldUserName, v))
+}
+
+// UserNameContainsFold applies the ContainsFold predicate on the "user_name" field.
+func UserNameContainsFold(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldContainsFold(FieldUserName, v))
+}
+
+// UserNotesEQ applies the EQ predicate on the "user_notes" field.
+func UserNotesEQ(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldUserNotes, v))
+}
+
+// UserNotesNEQ applies the NEQ predicate on the "user_notes" field.
+func UserNotesNEQ(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNEQ(FieldUserNotes, v))
+}
+
+// UserNotesIn applies the In predicate on the "user_notes" field.
+func UserNotesIn(vs ...string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIn(FieldUserNotes, vs...))
+}
+
+// UserNotesNotIn applies the NotIn predicate on the "user_notes" field.
+func UserNotesNotIn(vs ...string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotIn(FieldUserNotes, vs...))
+}
+
+// UserNotesGT applies the GT predicate on the "user_notes" field.
+func UserNotesGT(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGT(FieldUserNotes, v))
+}
+
+// UserNotesGTE applies the GTE predicate on the "user_notes" field.
+func UserNotesGTE(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGTE(FieldUserNotes, v))
+}
+
+// UserNotesLT applies the LT predicate on the "user_notes" field.
+func UserNotesLT(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLT(FieldUserNotes, v))
+}
+
+// UserNotesLTE applies the LTE predicate on the "user_notes" field.
+func UserNotesLTE(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLTE(FieldUserNotes, v))
+}
+
+// UserNotesContains applies the Contains predicate on the "user_notes" field.
+func UserNotesContains(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldContains(FieldUserNotes, v))
+}
+
+// UserNotesHasPrefix applies the HasPrefix predicate on the "user_notes" field.
+func UserNotesHasPrefix(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldHasPrefix(FieldUserNotes, v))
+}
+
+// UserNotesHasSuffix applies the HasSuffix predicate on the "user_notes" field.
+func UserNotesHasSuffix(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldHasSuffix(FieldUserNotes, v))
+}
+
+// UserNotesIsNil applies the IsNil predicate on the "user_notes" field.
+func UserNotesIsNil() predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIsNull(FieldUserNotes))
+}
+
+// UserNotesNotNil applies the NotNil predicate on the "user_notes" field.
+func UserNotesNotNil() predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotNull(FieldUserNotes))
+}
+
+// UserNotesEqualFold applies the EqualFold predicate on the "user_notes" field.
+func UserNotesEqualFold(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEqualFold(FieldUserNotes, v))
+}
+
+// UserNotesContainsFold applies the ContainsFold predicate on the "user_notes" field.
+func UserNotesContainsFold(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldContainsFold(FieldUserNotes, v))
+}
+
+// AmountEQ applies the EQ predicate on the "amount" field.
+func AmountEQ(v float64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldAmount, v))
+}
+
+// AmountNEQ applies the NEQ predicate on the "amount" field.
+func AmountNEQ(v float64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNEQ(FieldAmount, v))
+}
+
+// AmountIn applies the In predicate on the "amount" field.
+func AmountIn(vs ...float64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIn(FieldAmount, vs...))
+}
+
+// AmountNotIn applies the NotIn predicate on the "amount" field.
+func AmountNotIn(vs ...float64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotIn(FieldAmount, vs...))
+}
+
+// AmountGT applies the GT predicate on the "amount" field.
+func AmountGT(v float64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGT(FieldAmount, v))
+}
+
+// AmountGTE applies the GTE predicate on the "amount" field.
+func AmountGTE(v float64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGTE(FieldAmount, v))
+}
+
+// AmountLT applies the LT predicate on the "amount" field.
+func AmountLT(v float64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLT(FieldAmount, v))
+}
+
+// AmountLTE applies the LTE predicate on the "amount" field.
+func AmountLTE(v float64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLTE(FieldAmount, v))
+}
+
+// PayAmountEQ applies the EQ predicate on the "pay_amount" field.
+func PayAmountEQ(v float64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldPayAmount, v))
+}
+
+// PayAmountNEQ applies the NEQ predicate on the "pay_amount" field.
+func PayAmountNEQ(v float64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNEQ(FieldPayAmount, v))
+}
+
+// PayAmountIn applies the In predicate on the "pay_amount" field.
+func PayAmountIn(vs ...float64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIn(FieldPayAmount, vs...))
+}
+
+// PayAmountNotIn applies the NotIn predicate on the "pay_amount" field.
+func PayAmountNotIn(vs ...float64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotIn(FieldPayAmount, vs...))
+}
+
+// PayAmountGT applies the GT predicate on the "pay_amount" field.
+func PayAmountGT(v float64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGT(FieldPayAmount, v))
+}
+
+// PayAmountGTE applies the GTE predicate on the "pay_amount" field.
+func PayAmountGTE(v float64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGTE(FieldPayAmount, v))
+}
+
+// PayAmountLT applies the LT predicate on the "pay_amount" field.
+func PayAmountLT(v float64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLT(FieldPayAmount, v))
+}
+
+// PayAmountLTE applies the LTE predicate on the "pay_amount" field.
+func PayAmountLTE(v float64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLTE(FieldPayAmount, v))
+}
+
+// FeeRateEQ applies the EQ predicate on the "fee_rate" field.
+func FeeRateEQ(v float64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldFeeRate, v))
+}
+
+// FeeRateNEQ applies the NEQ predicate on the "fee_rate" field.
+func FeeRateNEQ(v float64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNEQ(FieldFeeRate, v))
+}
+
+// FeeRateIn applies the In predicate on the "fee_rate" field.
+func FeeRateIn(vs ...float64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIn(FieldFeeRate, vs...))
+}
+
+// FeeRateNotIn applies the NotIn predicate on the "fee_rate" field.
+func FeeRateNotIn(vs ...float64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotIn(FieldFeeRate, vs...))
+}
+
+// FeeRateGT applies the GT predicate on the "fee_rate" field.
+func FeeRateGT(v float64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGT(FieldFeeRate, v))
+}
+
+// FeeRateGTE applies the GTE predicate on the "fee_rate" field.
+func FeeRateGTE(v float64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGTE(FieldFeeRate, v))
+}
+
+// FeeRateLT applies the LT predicate on the "fee_rate" field.
+func FeeRateLT(v float64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLT(FieldFeeRate, v))
+}
+
+// FeeRateLTE applies the LTE predicate on the "fee_rate" field.
+func FeeRateLTE(v float64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLTE(FieldFeeRate, v))
+}
+
+// RechargeCodeEQ applies the EQ predicate on the "recharge_code" field.
+func RechargeCodeEQ(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldRechargeCode, v))
+}
+
+// RechargeCodeNEQ applies the NEQ predicate on the "recharge_code" field.
+func RechargeCodeNEQ(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNEQ(FieldRechargeCode, v))
+}
+
+// RechargeCodeIn applies the In predicate on the "recharge_code" field.
+func RechargeCodeIn(vs ...string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIn(FieldRechargeCode, vs...))
+}
+
+// RechargeCodeNotIn applies the NotIn predicate on the "recharge_code" field.
+func RechargeCodeNotIn(vs ...string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotIn(FieldRechargeCode, vs...))
+}
+
+// RechargeCodeGT applies the GT predicate on the "recharge_code" field.
+func RechargeCodeGT(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGT(FieldRechargeCode, v))
+}
+
+// RechargeCodeGTE applies the GTE predicate on the "recharge_code" field.
+func RechargeCodeGTE(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGTE(FieldRechargeCode, v))
+}
+
+// RechargeCodeLT applies the LT predicate on the "recharge_code" field.
+func RechargeCodeLT(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLT(FieldRechargeCode, v))
+}
+
+// RechargeCodeLTE applies the LTE predicate on the "recharge_code" field.
+func RechargeCodeLTE(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLTE(FieldRechargeCode, v))
+}
+
+// RechargeCodeContains applies the Contains predicate on the "recharge_code" field.
+func RechargeCodeContains(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldContains(FieldRechargeCode, v))
+}
+
+// RechargeCodeHasPrefix applies the HasPrefix predicate on the "recharge_code" field.
+func RechargeCodeHasPrefix(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldHasPrefix(FieldRechargeCode, v))
+}
+
+// RechargeCodeHasSuffix applies the HasSuffix predicate on the "recharge_code" field.
+func RechargeCodeHasSuffix(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldHasSuffix(FieldRechargeCode, v))
+}
+
+// RechargeCodeEqualFold applies the EqualFold predicate on the "recharge_code" field.
+func RechargeCodeEqualFold(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEqualFold(FieldRechargeCode, v))
+}
+
+// RechargeCodeContainsFold applies the ContainsFold predicate on the "recharge_code" field.
+func RechargeCodeContainsFold(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldContainsFold(FieldRechargeCode, v))
+}
+
+// OutTradeNoEQ applies the EQ predicate on the "out_trade_no" field.
+func OutTradeNoEQ(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldOutTradeNo, v))
+}
+
+// OutTradeNoNEQ applies the NEQ predicate on the "out_trade_no" field.
+func OutTradeNoNEQ(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNEQ(FieldOutTradeNo, v))
+}
+
+// OutTradeNoIn applies the In predicate on the "out_trade_no" field.
+func OutTradeNoIn(vs ...string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIn(FieldOutTradeNo, vs...))
+}
+
+// OutTradeNoNotIn applies the NotIn predicate on the "out_trade_no" field.
+func OutTradeNoNotIn(vs ...string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotIn(FieldOutTradeNo, vs...))
+}
+
+// OutTradeNoGT applies the GT predicate on the "out_trade_no" field.
+func OutTradeNoGT(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGT(FieldOutTradeNo, v))
+}
+
+// OutTradeNoGTE applies the GTE predicate on the "out_trade_no" field.
+func OutTradeNoGTE(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGTE(FieldOutTradeNo, v))
+}
+
+// OutTradeNoLT applies the LT predicate on the "out_trade_no" field.
+func OutTradeNoLT(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLT(FieldOutTradeNo, v))
+}
+
+// OutTradeNoLTE applies the LTE predicate on the "out_trade_no" field.
+func OutTradeNoLTE(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLTE(FieldOutTradeNo, v))
+}
+
+// OutTradeNoContains applies the Contains predicate on the "out_trade_no" field.
+func OutTradeNoContains(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldContains(FieldOutTradeNo, v))
+}
+
+// OutTradeNoHasPrefix applies the HasPrefix predicate on the "out_trade_no" field.
+func OutTradeNoHasPrefix(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldHasPrefix(FieldOutTradeNo, v))
+}
+
+// OutTradeNoHasSuffix applies the HasSuffix predicate on the "out_trade_no" field.
+func OutTradeNoHasSuffix(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldHasSuffix(FieldOutTradeNo, v))
+}
+
+// OutTradeNoEqualFold applies the EqualFold predicate on the "out_trade_no" field.
+func OutTradeNoEqualFold(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEqualFold(FieldOutTradeNo, v))
+}
+
+// OutTradeNoContainsFold applies the ContainsFold predicate on the "out_trade_no" field.
+func OutTradeNoContainsFold(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldContainsFold(FieldOutTradeNo, v))
+}
+
+// PaymentTypeEQ applies the EQ predicate on the "payment_type" field.
+func PaymentTypeEQ(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldPaymentType, v))
+}
+
+// PaymentTypeNEQ applies the NEQ predicate on the "payment_type" field.
+func PaymentTypeNEQ(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNEQ(FieldPaymentType, v))
+}
+
+// PaymentTypeIn applies the In predicate on the "payment_type" field.
+func PaymentTypeIn(vs ...string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIn(FieldPaymentType, vs...))
+}
+
+// PaymentTypeNotIn applies the NotIn predicate on the "payment_type" field.
+func PaymentTypeNotIn(vs ...string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotIn(FieldPaymentType, vs...))
+}
+
+// PaymentTypeGT applies the GT predicate on the "payment_type" field.
+func PaymentTypeGT(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGT(FieldPaymentType, v))
+}
+
+// PaymentTypeGTE applies the GTE predicate on the "payment_type" field.
+func PaymentTypeGTE(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGTE(FieldPaymentType, v))
+}
+
+// PaymentTypeLT applies the LT predicate on the "payment_type" field.
+func PaymentTypeLT(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLT(FieldPaymentType, v))
+}
+
+// PaymentTypeLTE applies the LTE predicate on the "payment_type" field.
+func PaymentTypeLTE(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLTE(FieldPaymentType, v))
+}
+
+// PaymentTypeContains applies the Contains predicate on the "payment_type" field.
+func PaymentTypeContains(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldContains(FieldPaymentType, v))
+}
+
+// PaymentTypeHasPrefix applies the HasPrefix predicate on the "payment_type" field.
+func PaymentTypeHasPrefix(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldHasPrefix(FieldPaymentType, v))
+}
+
+// PaymentTypeHasSuffix applies the HasSuffix predicate on the "payment_type" field.
+func PaymentTypeHasSuffix(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldHasSuffix(FieldPaymentType, v))
+}
+
+// PaymentTypeEqualFold applies the EqualFold predicate on the "payment_type" field.
+func PaymentTypeEqualFold(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEqualFold(FieldPaymentType, v))
+}
+
+// PaymentTypeContainsFold applies the ContainsFold predicate on the "payment_type" field.
+func PaymentTypeContainsFold(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldContainsFold(FieldPaymentType, v))
+}
+
+// PaymentTradeNoEQ applies the EQ predicate on the "payment_trade_no" field.
+func PaymentTradeNoEQ(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldPaymentTradeNo, v))
+}
+
+// PaymentTradeNoNEQ applies the NEQ predicate on the "payment_trade_no" field.
+func PaymentTradeNoNEQ(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNEQ(FieldPaymentTradeNo, v))
+}
+
+// PaymentTradeNoIn applies the In predicate on the "payment_trade_no" field.
+func PaymentTradeNoIn(vs ...string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIn(FieldPaymentTradeNo, vs...))
+}
+
+// PaymentTradeNoNotIn applies the NotIn predicate on the "payment_trade_no" field.
+func PaymentTradeNoNotIn(vs ...string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotIn(FieldPaymentTradeNo, vs...))
+}
+
+// PaymentTradeNoGT applies the GT predicate on the "payment_trade_no" field.
+func PaymentTradeNoGT(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGT(FieldPaymentTradeNo, v))
+}
+
+// PaymentTradeNoGTE applies the GTE predicate on the "payment_trade_no" field.
+func PaymentTradeNoGTE(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGTE(FieldPaymentTradeNo, v))
+}
+
+// PaymentTradeNoLT applies the LT predicate on the "payment_trade_no" field.
+func PaymentTradeNoLT(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLT(FieldPaymentTradeNo, v))
+}
+
+// PaymentTradeNoLTE applies the LTE predicate on the "payment_trade_no" field.
+func PaymentTradeNoLTE(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLTE(FieldPaymentTradeNo, v))
+}
+
+// PaymentTradeNoContains applies the Contains predicate on the "payment_trade_no" field.
+func PaymentTradeNoContains(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldContains(FieldPaymentTradeNo, v))
+}
+
+// PaymentTradeNoHasPrefix applies the HasPrefix predicate on the "payment_trade_no" field.
+func PaymentTradeNoHasPrefix(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldHasPrefix(FieldPaymentTradeNo, v))
+}
+
+// PaymentTradeNoHasSuffix applies the HasSuffix predicate on the "payment_trade_no" field.
+func PaymentTradeNoHasSuffix(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldHasSuffix(FieldPaymentTradeNo, v))
+}
+
+// PaymentTradeNoEqualFold applies the EqualFold predicate on the "payment_trade_no" field.
+func PaymentTradeNoEqualFold(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEqualFold(FieldPaymentTradeNo, v))
+}
+
+// PaymentTradeNoContainsFold applies the ContainsFold predicate on the "payment_trade_no" field.
+func PaymentTradeNoContainsFold(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldContainsFold(FieldPaymentTradeNo, v))
+}
+
+// PayURLEQ applies the EQ predicate on the "pay_url" field.
+func PayURLEQ(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldPayURL, v))
+}
+
+// PayURLNEQ applies the NEQ predicate on the "pay_url" field.
+func PayURLNEQ(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNEQ(FieldPayURL, v))
+}
+
+// PayURLIn applies the In predicate on the "pay_url" field.
+func PayURLIn(vs ...string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIn(FieldPayURL, vs...))
+}
+
+// PayURLNotIn applies the NotIn predicate on the "pay_url" field.
+func PayURLNotIn(vs ...string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotIn(FieldPayURL, vs...))
+}
+
+// PayURLGT applies the GT predicate on the "pay_url" field.
+func PayURLGT(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGT(FieldPayURL, v))
+}
+
+// PayURLGTE applies the GTE predicate on the "pay_url" field.
+func PayURLGTE(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGTE(FieldPayURL, v))
+}
+
+// PayURLLT applies the LT predicate on the "pay_url" field.
+func PayURLLT(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLT(FieldPayURL, v))
+}
+
+// PayURLLTE applies the LTE predicate on the "pay_url" field.
+func PayURLLTE(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLTE(FieldPayURL, v))
+}
+
+// PayURLContains applies the Contains predicate on the "pay_url" field.
+func PayURLContains(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldContains(FieldPayURL, v))
+}
+
+// PayURLHasPrefix applies the HasPrefix predicate on the "pay_url" field.
+func PayURLHasPrefix(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldHasPrefix(FieldPayURL, v))
+}
+
+// PayURLHasSuffix applies the HasSuffix predicate on the "pay_url" field.
+func PayURLHasSuffix(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldHasSuffix(FieldPayURL, v))
+}
+
+// PayURLIsNil applies the IsNil predicate on the "pay_url" field.
+func PayURLIsNil() predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIsNull(FieldPayURL))
+}
+
+// PayURLNotNil applies the NotNil predicate on the "pay_url" field.
+func PayURLNotNil() predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotNull(FieldPayURL))
+}
+
+// PayURLEqualFold applies the EqualFold predicate on the "pay_url" field.
+func PayURLEqualFold(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEqualFold(FieldPayURL, v))
+}
+
+// PayURLContainsFold applies the ContainsFold predicate on the "pay_url" field.
+func PayURLContainsFold(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldContainsFold(FieldPayURL, v))
+}
+
+// QrCodeEQ applies the EQ predicate on the "qr_code" field.
+func QrCodeEQ(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldQrCode, v))
+}
+
+// QrCodeNEQ applies the NEQ predicate on the "qr_code" field.
+func QrCodeNEQ(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNEQ(FieldQrCode, v))
+}
+
+// QrCodeIn applies the In predicate on the "qr_code" field.
+func QrCodeIn(vs ...string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIn(FieldQrCode, vs...))
+}
+
+// QrCodeNotIn applies the NotIn predicate on the "qr_code" field.
+func QrCodeNotIn(vs ...string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotIn(FieldQrCode, vs...))
+}
+
+// QrCodeGT applies the GT predicate on the "qr_code" field.
+func QrCodeGT(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGT(FieldQrCode, v))
+}
+
+// QrCodeGTE applies the GTE predicate on the "qr_code" field.
+func QrCodeGTE(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGTE(FieldQrCode, v))
+}
+
+// QrCodeLT applies the LT predicate on the "qr_code" field.
+func QrCodeLT(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLT(FieldQrCode, v))
+}
+
+// QrCodeLTE applies the LTE predicate on the "qr_code" field.
+func QrCodeLTE(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLTE(FieldQrCode, v))
+}
+
+// QrCodeContains applies the Contains predicate on the "qr_code" field.
+func QrCodeContains(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldContains(FieldQrCode, v))
+}
+
+// QrCodeHasPrefix applies the HasPrefix predicate on the "qr_code" field.
+func QrCodeHasPrefix(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldHasPrefix(FieldQrCode, v))
+}
+
+// QrCodeHasSuffix applies the HasSuffix predicate on the "qr_code" field.
+func QrCodeHasSuffix(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldHasSuffix(FieldQrCode, v))
+}
+
+// QrCodeIsNil applies the IsNil predicate on the "qr_code" field.
+func QrCodeIsNil() predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIsNull(FieldQrCode))
+}
+
+// QrCodeNotNil applies the NotNil predicate on the "qr_code" field.
+func QrCodeNotNil() predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotNull(FieldQrCode))
+}
+
+// QrCodeEqualFold applies the EqualFold predicate on the "qr_code" field.
+func QrCodeEqualFold(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEqualFold(FieldQrCode, v))
+}
+
+// QrCodeContainsFold applies the ContainsFold predicate on the "qr_code" field.
+func QrCodeContainsFold(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldContainsFold(FieldQrCode, v))
+}
+
+// QrCodeImgEQ applies the EQ predicate on the "qr_code_img" field.
+func QrCodeImgEQ(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldQrCodeImg, v))
+}
+
+// QrCodeImgNEQ applies the NEQ predicate on the "qr_code_img" field.
+func QrCodeImgNEQ(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNEQ(FieldQrCodeImg, v))
+}
+
+// QrCodeImgIn applies the In predicate on the "qr_code_img" field.
+func QrCodeImgIn(vs ...string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIn(FieldQrCodeImg, vs...))
+}
+
+// QrCodeImgNotIn applies the NotIn predicate on the "qr_code_img" field.
+func QrCodeImgNotIn(vs ...string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotIn(FieldQrCodeImg, vs...))
+}
+
+// QrCodeImgGT applies the GT predicate on the "qr_code_img" field.
+func QrCodeImgGT(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGT(FieldQrCodeImg, v))
+}
+
+// QrCodeImgGTE applies the GTE predicate on the "qr_code_img" field.
+func QrCodeImgGTE(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGTE(FieldQrCodeImg, v))
+}
+
+// QrCodeImgLT applies the LT predicate on the "qr_code_img" field.
+func QrCodeImgLT(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLT(FieldQrCodeImg, v))
+}
+
+// QrCodeImgLTE applies the LTE predicate on the "qr_code_img" field.
+func QrCodeImgLTE(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLTE(FieldQrCodeImg, v))
+}
+
+// QrCodeImgContains applies the Contains predicate on the "qr_code_img" field.
+func QrCodeImgContains(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldContains(FieldQrCodeImg, v))
+}
+
+// QrCodeImgHasPrefix applies the HasPrefix predicate on the "qr_code_img" field.
+func QrCodeImgHasPrefix(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldHasPrefix(FieldQrCodeImg, v))
+}
+
+// QrCodeImgHasSuffix applies the HasSuffix predicate on the "qr_code_img" field.
+func QrCodeImgHasSuffix(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldHasSuffix(FieldQrCodeImg, v))
+}
+
+// QrCodeImgIsNil applies the IsNil predicate on the "qr_code_img" field.
+func QrCodeImgIsNil() predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIsNull(FieldQrCodeImg))
+}
+
+// QrCodeImgNotNil applies the NotNil predicate on the "qr_code_img" field.
+func QrCodeImgNotNil() predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotNull(FieldQrCodeImg))
+}
+
+// QrCodeImgEqualFold applies the EqualFold predicate on the "qr_code_img" field.
+func QrCodeImgEqualFold(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEqualFold(FieldQrCodeImg, v))
+}
+
+// QrCodeImgContainsFold applies the ContainsFold predicate on the "qr_code_img" field.
+func QrCodeImgContainsFold(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldContainsFold(FieldQrCodeImg, v))
+}
+
+// OrderTypeEQ applies the EQ predicate on the "order_type" field.
+func OrderTypeEQ(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldOrderType, v))
+}
+
+// OrderTypeNEQ applies the NEQ predicate on the "order_type" field.
+func OrderTypeNEQ(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNEQ(FieldOrderType, v))
+}
+
+// OrderTypeIn applies the In predicate on the "order_type" field.
+func OrderTypeIn(vs ...string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIn(FieldOrderType, vs...))
+}
+
+// OrderTypeNotIn applies the NotIn predicate on the "order_type" field.
+func OrderTypeNotIn(vs ...string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotIn(FieldOrderType, vs...))
+}
+
+// OrderTypeGT applies the GT predicate on the "order_type" field.
+func OrderTypeGT(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGT(FieldOrderType, v))
+}
+
+// OrderTypeGTE applies the GTE predicate on the "order_type" field.
+func OrderTypeGTE(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGTE(FieldOrderType, v))
+}
+
+// OrderTypeLT applies the LT predicate on the "order_type" field.
+func OrderTypeLT(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLT(FieldOrderType, v))
+}
+
+// OrderTypeLTE applies the LTE predicate on the "order_type" field.
+func OrderTypeLTE(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLTE(FieldOrderType, v))
+}
+
+// OrderTypeContains applies the Contains predicate on the "order_type" field.
+func OrderTypeContains(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldContains(FieldOrderType, v))
+}
+
+// OrderTypeHasPrefix applies the HasPrefix predicate on the "order_type" field.
+func OrderTypeHasPrefix(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldHasPrefix(FieldOrderType, v))
+}
+
+// OrderTypeHasSuffix applies the HasSuffix predicate on the "order_type" field.
+func OrderTypeHasSuffix(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldHasSuffix(FieldOrderType, v))
+}
+
+// OrderTypeEqualFold applies the EqualFold predicate on the "order_type" field.
+func OrderTypeEqualFold(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEqualFold(FieldOrderType, v))
+}
+
+// OrderTypeContainsFold applies the ContainsFold predicate on the "order_type" field.
+func OrderTypeContainsFold(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldContainsFold(FieldOrderType, v))
+}
+
+// PlanIDEQ applies the EQ predicate on the "plan_id" field.
+func PlanIDEQ(v int64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldPlanID, v))
+}
+
+// PlanIDNEQ applies the NEQ predicate on the "plan_id" field.
+func PlanIDNEQ(v int64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNEQ(FieldPlanID, v))
+}
+
+// PlanIDIn applies the In predicate on the "plan_id" field.
+func PlanIDIn(vs ...int64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIn(FieldPlanID, vs...))
+}
+
+// PlanIDNotIn applies the NotIn predicate on the "plan_id" field.
+func PlanIDNotIn(vs ...int64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotIn(FieldPlanID, vs...))
+}
+
+// PlanIDGT applies the GT predicate on the "plan_id" field.
+func PlanIDGT(v int64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGT(FieldPlanID, v))
+}
+
+// PlanIDGTE applies the GTE predicate on the "plan_id" field.
+func PlanIDGTE(v int64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGTE(FieldPlanID, v))
+}
+
+// PlanIDLT applies the LT predicate on the "plan_id" field.
+func PlanIDLT(v int64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLT(FieldPlanID, v))
+}
+
+// PlanIDLTE applies the LTE predicate on the "plan_id" field.
+func PlanIDLTE(v int64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLTE(FieldPlanID, v))
+}
+
+// PlanIDIsNil applies the IsNil predicate on the "plan_id" field.
+func PlanIDIsNil() predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIsNull(FieldPlanID))
+}
+
+// PlanIDNotNil applies the NotNil predicate on the "plan_id" field.
+func PlanIDNotNil() predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotNull(FieldPlanID))
+}
+
+// SubscriptionGroupIDEQ applies the EQ predicate on the "subscription_group_id" field.
+func SubscriptionGroupIDEQ(v int64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldSubscriptionGroupID, v))
+}
+
+// SubscriptionGroupIDNEQ applies the NEQ predicate on the "subscription_group_id" field.
+func SubscriptionGroupIDNEQ(v int64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNEQ(FieldSubscriptionGroupID, v))
+}
+
+// SubscriptionGroupIDIn applies the In predicate on the "subscription_group_id" field.
+func SubscriptionGroupIDIn(vs ...int64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIn(FieldSubscriptionGroupID, vs...))
+}
+
+// SubscriptionGroupIDNotIn applies the NotIn predicate on the "subscription_group_id" field.
+func SubscriptionGroupIDNotIn(vs ...int64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotIn(FieldSubscriptionGroupID, vs...))
+}
+
+// SubscriptionGroupIDGT applies the GT predicate on the "subscription_group_id" field.
+func SubscriptionGroupIDGT(v int64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGT(FieldSubscriptionGroupID, v))
+}
+
+// SubscriptionGroupIDGTE applies the GTE predicate on the "subscription_group_id" field.
+func SubscriptionGroupIDGTE(v int64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGTE(FieldSubscriptionGroupID, v))
+}
+
+// SubscriptionGroupIDLT applies the LT predicate on the "subscription_group_id" field.
+func SubscriptionGroupIDLT(v int64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLT(FieldSubscriptionGroupID, v))
+}
+
+// SubscriptionGroupIDLTE applies the LTE predicate on the "subscription_group_id" field.
+func SubscriptionGroupIDLTE(v int64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLTE(FieldSubscriptionGroupID, v))
+}
+
+// SubscriptionGroupIDIsNil applies the IsNil predicate on the "subscription_group_id" field.
+func SubscriptionGroupIDIsNil() predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIsNull(FieldSubscriptionGroupID))
+}
+
+// SubscriptionGroupIDNotNil applies the NotNil predicate on the "subscription_group_id" field.
+func SubscriptionGroupIDNotNil() predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotNull(FieldSubscriptionGroupID))
+}
+
+// SubscriptionDaysEQ applies the EQ predicate on the "subscription_days" field.
+func SubscriptionDaysEQ(v int) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldSubscriptionDays, v))
+}
+
+// SubscriptionDaysNEQ applies the NEQ predicate on the "subscription_days" field.
+func SubscriptionDaysNEQ(v int) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNEQ(FieldSubscriptionDays, v))
+}
+
+// SubscriptionDaysIn applies the In predicate on the "subscription_days" field.
+func SubscriptionDaysIn(vs ...int) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIn(FieldSubscriptionDays, vs...))
+}
+
+// SubscriptionDaysNotIn applies the NotIn predicate on the "subscription_days" field.
+func SubscriptionDaysNotIn(vs ...int) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotIn(FieldSubscriptionDays, vs...))
+}
+
+// SubscriptionDaysGT applies the GT predicate on the "subscription_days" field.
+func SubscriptionDaysGT(v int) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGT(FieldSubscriptionDays, v))
+}
+
+// SubscriptionDaysGTE applies the GTE predicate on the "subscription_days" field.
+func SubscriptionDaysGTE(v int) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGTE(FieldSubscriptionDays, v))
+}
+
+// SubscriptionDaysLT applies the LT predicate on the "subscription_days" field.
+func SubscriptionDaysLT(v int) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLT(FieldSubscriptionDays, v))
+}
+
+// SubscriptionDaysLTE applies the LTE predicate on the "subscription_days" field.
+func SubscriptionDaysLTE(v int) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLTE(FieldSubscriptionDays, v))
+}
+
+// SubscriptionDaysIsNil applies the IsNil predicate on the "subscription_days" field.
+func SubscriptionDaysIsNil() predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIsNull(FieldSubscriptionDays))
+}
+
+// SubscriptionDaysNotNil applies the NotNil predicate on the "subscription_days" field.
+func SubscriptionDaysNotNil() predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotNull(FieldSubscriptionDays))
+}
+
+// ProviderInstanceIDEQ applies the EQ predicate on the "provider_instance_id" field.
+func ProviderInstanceIDEQ(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldProviderInstanceID, v))
+}
+
+// ProviderInstanceIDNEQ applies the NEQ predicate on the "provider_instance_id" field.
+func ProviderInstanceIDNEQ(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNEQ(FieldProviderInstanceID, v))
+}
+
+// ProviderInstanceIDIn applies the In predicate on the "provider_instance_id" field.
+func ProviderInstanceIDIn(vs ...string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIn(FieldProviderInstanceID, vs...))
+}
+
+// ProviderInstanceIDNotIn applies the NotIn predicate on the "provider_instance_id" field.
+func ProviderInstanceIDNotIn(vs ...string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotIn(FieldProviderInstanceID, vs...))
+}
+
+// ProviderInstanceIDGT applies the GT predicate on the "provider_instance_id" field.
+func ProviderInstanceIDGT(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGT(FieldProviderInstanceID, v))
+}
+
+// ProviderInstanceIDGTE applies the GTE predicate on the "provider_instance_id" field.
+func ProviderInstanceIDGTE(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGTE(FieldProviderInstanceID, v))
+}
+
+// ProviderInstanceIDLT applies the LT predicate on the "provider_instance_id" field.
+func ProviderInstanceIDLT(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLT(FieldProviderInstanceID, v))
+}
+
+// ProviderInstanceIDLTE applies the LTE predicate on the "provider_instance_id" field.
+func ProviderInstanceIDLTE(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLTE(FieldProviderInstanceID, v))
+}
+
+// ProviderInstanceIDContains applies the Contains predicate on the "provider_instance_id" field.
+func ProviderInstanceIDContains(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldContains(FieldProviderInstanceID, v))
+}
+
+// ProviderInstanceIDHasPrefix applies the HasPrefix predicate on the "provider_instance_id" field.
+func ProviderInstanceIDHasPrefix(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldHasPrefix(FieldProviderInstanceID, v))
+}
+
+// ProviderInstanceIDHasSuffix applies the HasSuffix predicate on the "provider_instance_id" field.
+func ProviderInstanceIDHasSuffix(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldHasSuffix(FieldProviderInstanceID, v))
+}
+
+// ProviderInstanceIDIsNil applies the IsNil predicate on the "provider_instance_id" field.
+func ProviderInstanceIDIsNil() predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIsNull(FieldProviderInstanceID))
+}
+
+// ProviderInstanceIDNotNil applies the NotNil predicate on the "provider_instance_id" field.
+func ProviderInstanceIDNotNil() predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotNull(FieldProviderInstanceID))
+}
+
+// ProviderInstanceIDEqualFold applies the EqualFold predicate on the "provider_instance_id" field.
+func ProviderInstanceIDEqualFold(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEqualFold(FieldProviderInstanceID, v))
+}
+
+// ProviderInstanceIDContainsFold applies the ContainsFold predicate on the "provider_instance_id" field.
+func ProviderInstanceIDContainsFold(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldContainsFold(FieldProviderInstanceID, v))
+}
+
+// StatusEQ applies the EQ predicate on the "status" field.
+func StatusEQ(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldStatus, v))
+}
+
+// StatusNEQ applies the NEQ predicate on the "status" field.
+func StatusNEQ(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNEQ(FieldStatus, v))
+}
+
+// StatusIn applies the In predicate on the "status" field.
+func StatusIn(vs ...string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIn(FieldStatus, vs...))
+}
+
+// StatusNotIn applies the NotIn predicate on the "status" field.
+func StatusNotIn(vs ...string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotIn(FieldStatus, vs...))
+}
+
+// StatusGT applies the GT predicate on the "status" field.
+func StatusGT(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGT(FieldStatus, v))
+}
+
+// StatusGTE applies the GTE predicate on the "status" field.
+func StatusGTE(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGTE(FieldStatus, v))
+}
+
+// StatusLT applies the LT predicate on the "status" field.
+func StatusLT(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLT(FieldStatus, v))
+}
+
+// StatusLTE applies the LTE predicate on the "status" field.
+func StatusLTE(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLTE(FieldStatus, v))
+}
+
+// StatusContains applies the Contains predicate on the "status" field.
+func StatusContains(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldContains(FieldStatus, v))
+}
+
+// StatusHasPrefix applies the HasPrefix predicate on the "status" field.
+func StatusHasPrefix(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldHasPrefix(FieldStatus, v))
+}
+
+// StatusHasSuffix applies the HasSuffix predicate on the "status" field.
+func StatusHasSuffix(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldHasSuffix(FieldStatus, v))
+}
+
+// StatusEqualFold applies the EqualFold predicate on the "status" field.
+func StatusEqualFold(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEqualFold(FieldStatus, v))
+}
+
+// StatusContainsFold applies the ContainsFold predicate on the "status" field.
+func StatusContainsFold(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldContainsFold(FieldStatus, v))
+}
+
+// RefundAmountEQ applies the EQ predicate on the "refund_amount" field.
+func RefundAmountEQ(v float64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldRefundAmount, v))
+}
+
+// RefundAmountNEQ applies the NEQ predicate on the "refund_amount" field.
+func RefundAmountNEQ(v float64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNEQ(FieldRefundAmount, v))
+}
+
+// RefundAmountIn applies the In predicate on the "refund_amount" field.
+func RefundAmountIn(vs ...float64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIn(FieldRefundAmount, vs...))
+}
+
+// RefundAmountNotIn applies the NotIn predicate on the "refund_amount" field.
+func RefundAmountNotIn(vs ...float64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotIn(FieldRefundAmount, vs...))
+}
+
+// RefundAmountGT applies the GT predicate on the "refund_amount" field.
+func RefundAmountGT(v float64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGT(FieldRefundAmount, v))
+}
+
+// RefundAmountGTE applies the GTE predicate on the "refund_amount" field.
+func RefundAmountGTE(v float64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGTE(FieldRefundAmount, v))
+}
+
+// RefundAmountLT applies the LT predicate on the "refund_amount" field.
+func RefundAmountLT(v float64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLT(FieldRefundAmount, v))
+}
+
+// RefundAmountLTE applies the LTE predicate on the "refund_amount" field.
+func RefundAmountLTE(v float64) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLTE(FieldRefundAmount, v))
+}
+
+// RefundReasonEQ applies the EQ predicate on the "refund_reason" field.
+func RefundReasonEQ(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldRefundReason, v))
+}
+
+// RefundReasonNEQ applies the NEQ predicate on the "refund_reason" field.
+func RefundReasonNEQ(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNEQ(FieldRefundReason, v))
+}
+
+// RefundReasonIn applies the In predicate on the "refund_reason" field.
+func RefundReasonIn(vs ...string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIn(FieldRefundReason, vs...))
+}
+
+// RefundReasonNotIn applies the NotIn predicate on the "refund_reason" field.
+func RefundReasonNotIn(vs ...string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotIn(FieldRefundReason, vs...))
+}
+
+// RefundReasonGT applies the GT predicate on the "refund_reason" field.
+func RefundReasonGT(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGT(FieldRefundReason, v))
+}
+
+// RefundReasonGTE applies the GTE predicate on the "refund_reason" field.
+func RefundReasonGTE(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGTE(FieldRefundReason, v))
+}
+
+// RefundReasonLT applies the LT predicate on the "refund_reason" field.
+func RefundReasonLT(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLT(FieldRefundReason, v))
+}
+
+// RefundReasonLTE applies the LTE predicate on the "refund_reason" field.
+func RefundReasonLTE(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLTE(FieldRefundReason, v))
+}
+
+// RefundReasonContains applies the Contains predicate on the "refund_reason" field.
+func RefundReasonContains(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldContains(FieldRefundReason, v))
+}
+
+// RefundReasonHasPrefix applies the HasPrefix predicate on the "refund_reason" field.
+func RefundReasonHasPrefix(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldHasPrefix(FieldRefundReason, v))
+}
+
+// RefundReasonHasSuffix applies the HasSuffix predicate on the "refund_reason" field.
+func RefundReasonHasSuffix(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldHasSuffix(FieldRefundReason, v))
+}
+
+// RefundReasonIsNil applies the IsNil predicate on the "refund_reason" field.
+func RefundReasonIsNil() predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIsNull(FieldRefundReason))
+}
+
+// RefundReasonNotNil applies the NotNil predicate on the "refund_reason" field.
+func RefundReasonNotNil() predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotNull(FieldRefundReason))
+}
+
+// RefundReasonEqualFold applies the EqualFold predicate on the "refund_reason" field.
+func RefundReasonEqualFold(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEqualFold(FieldRefundReason, v))
+}
+
+// RefundReasonContainsFold applies the ContainsFold predicate on the "refund_reason" field.
+func RefundReasonContainsFold(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldContainsFold(FieldRefundReason, v))
+}
+
+// RefundAtEQ applies the EQ predicate on the "refund_at" field.
+func RefundAtEQ(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldRefundAt, v))
+}
+
+// RefundAtNEQ applies the NEQ predicate on the "refund_at" field.
+func RefundAtNEQ(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNEQ(FieldRefundAt, v))
+}
+
+// RefundAtIn applies the In predicate on the "refund_at" field.
+func RefundAtIn(vs ...time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIn(FieldRefundAt, vs...))
+}
+
+// RefundAtNotIn applies the NotIn predicate on the "refund_at" field.
+func RefundAtNotIn(vs ...time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotIn(FieldRefundAt, vs...))
+}
+
+// RefundAtGT applies the GT predicate on the "refund_at" field.
+func RefundAtGT(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGT(FieldRefundAt, v))
+}
+
+// RefundAtGTE applies the GTE predicate on the "refund_at" field.
+func RefundAtGTE(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGTE(FieldRefundAt, v))
+}
+
+// RefundAtLT applies the LT predicate on the "refund_at" field.
+func RefundAtLT(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLT(FieldRefundAt, v))
+}
+
+// RefundAtLTE applies the LTE predicate on the "refund_at" field.
+func RefundAtLTE(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLTE(FieldRefundAt, v))
+}
+
+// RefundAtIsNil applies the IsNil predicate on the "refund_at" field.
+func RefundAtIsNil() predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIsNull(FieldRefundAt))
+}
+
+// RefundAtNotNil applies the NotNil predicate on the "refund_at" field.
+func RefundAtNotNil() predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotNull(FieldRefundAt))
+}
+
+// ForceRefundEQ applies the EQ predicate on the "force_refund" field.
+func ForceRefundEQ(v bool) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldForceRefund, v))
+}
+
+// ForceRefundNEQ applies the NEQ predicate on the "force_refund" field.
+func ForceRefundNEQ(v bool) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNEQ(FieldForceRefund, v))
+}
+
+// RefundRequestedAtEQ applies the EQ predicate on the "refund_requested_at" field.
+func RefundRequestedAtEQ(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldRefundRequestedAt, v))
+}
+
+// RefundRequestedAtNEQ applies the NEQ predicate on the "refund_requested_at" field.
+func RefundRequestedAtNEQ(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNEQ(FieldRefundRequestedAt, v))
+}
+
+// RefundRequestedAtIn applies the In predicate on the "refund_requested_at" field.
+func RefundRequestedAtIn(vs ...time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIn(FieldRefundRequestedAt, vs...))
+}
+
+// RefundRequestedAtNotIn applies the NotIn predicate on the "refund_requested_at" field.
+func RefundRequestedAtNotIn(vs ...time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotIn(FieldRefundRequestedAt, vs...))
+}
+
+// RefundRequestedAtGT applies the GT predicate on the "refund_requested_at" field.
+func RefundRequestedAtGT(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGT(FieldRefundRequestedAt, v))
+}
+
+// RefundRequestedAtGTE applies the GTE predicate on the "refund_requested_at" field.
+func RefundRequestedAtGTE(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGTE(FieldRefundRequestedAt, v))
+}
+
+// RefundRequestedAtLT applies the LT predicate on the "refund_requested_at" field.
+func RefundRequestedAtLT(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLT(FieldRefundRequestedAt, v))
+}
+
+// RefundRequestedAtLTE applies the LTE predicate on the "refund_requested_at" field.
+func RefundRequestedAtLTE(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLTE(FieldRefundRequestedAt, v))
+}
+
+// RefundRequestedAtIsNil applies the IsNil predicate on the "refund_requested_at" field.
+func RefundRequestedAtIsNil() predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIsNull(FieldRefundRequestedAt))
+}
+
+// RefundRequestedAtNotNil applies the NotNil predicate on the "refund_requested_at" field.
+func RefundRequestedAtNotNil() predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotNull(FieldRefundRequestedAt))
+}
+
+// RefundRequestReasonEQ applies the EQ predicate on the "refund_request_reason" field.
+func RefundRequestReasonEQ(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldRefundRequestReason, v))
+}
+
+// RefundRequestReasonNEQ applies the NEQ predicate on the "refund_request_reason" field.
+func RefundRequestReasonNEQ(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNEQ(FieldRefundRequestReason, v))
+}
+
+// RefundRequestReasonIn applies the In predicate on the "refund_request_reason" field.
+func RefundRequestReasonIn(vs ...string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIn(FieldRefundRequestReason, vs...))
+}
+
+// RefundRequestReasonNotIn applies the NotIn predicate on the "refund_request_reason" field.
+func RefundRequestReasonNotIn(vs ...string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotIn(FieldRefundRequestReason, vs...))
+}
+
+// RefundRequestReasonGT applies the GT predicate on the "refund_request_reason" field.
+func RefundRequestReasonGT(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGT(FieldRefundRequestReason, v))
+}
+
+// RefundRequestReasonGTE applies the GTE predicate on the "refund_request_reason" field.
+func RefundRequestReasonGTE(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGTE(FieldRefundRequestReason, v))
+}
+
+// RefundRequestReasonLT applies the LT predicate on the "refund_request_reason" field.
+func RefundRequestReasonLT(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLT(FieldRefundRequestReason, v))
+}
+
+// RefundRequestReasonLTE applies the LTE predicate on the "refund_request_reason" field.
+func RefundRequestReasonLTE(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLTE(FieldRefundRequestReason, v))
+}
+
+// RefundRequestReasonContains applies the Contains predicate on the "refund_request_reason" field.
+func RefundRequestReasonContains(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldContains(FieldRefundRequestReason, v))
+}
+
+// RefundRequestReasonHasPrefix applies the HasPrefix predicate on the "refund_request_reason" field.
+func RefundRequestReasonHasPrefix(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldHasPrefix(FieldRefundRequestReason, v))
+}
+
+// RefundRequestReasonHasSuffix applies the HasSuffix predicate on the "refund_request_reason" field.
+func RefundRequestReasonHasSuffix(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldHasSuffix(FieldRefundRequestReason, v))
+}
+
+// RefundRequestReasonIsNil applies the IsNil predicate on the "refund_request_reason" field.
+func RefundRequestReasonIsNil() predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIsNull(FieldRefundRequestReason))
+}
+
+// RefundRequestReasonNotNil applies the NotNil predicate on the "refund_request_reason" field.
+func RefundRequestReasonNotNil() predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotNull(FieldRefundRequestReason))
+}
+
+// RefundRequestReasonEqualFold applies the EqualFold predicate on the "refund_request_reason" field.
+func RefundRequestReasonEqualFold(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEqualFold(FieldRefundRequestReason, v))
+}
+
+// RefundRequestReasonContainsFold applies the ContainsFold predicate on the "refund_request_reason" field.
+func RefundRequestReasonContainsFold(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldContainsFold(FieldRefundRequestReason, v))
+}
+
+// RefundRequestedByEQ applies the EQ predicate on the "refund_requested_by" field.
+func RefundRequestedByEQ(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldRefundRequestedBy, v))
+}
+
+// RefundRequestedByNEQ applies the NEQ predicate on the "refund_requested_by" field.
+func RefundRequestedByNEQ(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNEQ(FieldRefundRequestedBy, v))
+}
+
+// RefundRequestedByIn applies the In predicate on the "refund_requested_by" field.
+func RefundRequestedByIn(vs ...string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIn(FieldRefundRequestedBy, vs...))
+}
+
+// RefundRequestedByNotIn applies the NotIn predicate on the "refund_requested_by" field.
+func RefundRequestedByNotIn(vs ...string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotIn(FieldRefundRequestedBy, vs...))
+}
+
+// RefundRequestedByGT applies the GT predicate on the "refund_requested_by" field.
+func RefundRequestedByGT(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGT(FieldRefundRequestedBy, v))
+}
+
+// RefundRequestedByGTE applies the GTE predicate on the "refund_requested_by" field.
+func RefundRequestedByGTE(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGTE(FieldRefundRequestedBy, v))
+}
+
+// RefundRequestedByLT applies the LT predicate on the "refund_requested_by" field.
+func RefundRequestedByLT(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLT(FieldRefundRequestedBy, v))
+}
+
+// RefundRequestedByLTE applies the LTE predicate on the "refund_requested_by" field.
+func RefundRequestedByLTE(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLTE(FieldRefundRequestedBy, v))
+}
+
+// RefundRequestedByContains applies the Contains predicate on the "refund_requested_by" field.
+func RefundRequestedByContains(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldContains(FieldRefundRequestedBy, v))
+}
+
+// RefundRequestedByHasPrefix applies the HasPrefix predicate on the "refund_requested_by" field.
+func RefundRequestedByHasPrefix(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldHasPrefix(FieldRefundRequestedBy, v))
+}
+
+// RefundRequestedByHasSuffix applies the HasSuffix predicate on the "refund_requested_by" field.
+func RefundRequestedByHasSuffix(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldHasSuffix(FieldRefundRequestedBy, v))
+}
+
+// RefundRequestedByIsNil applies the IsNil predicate on the "refund_requested_by" field.
+func RefundRequestedByIsNil() predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIsNull(FieldRefundRequestedBy))
+}
+
+// RefundRequestedByNotNil applies the NotNil predicate on the "refund_requested_by" field.
+func RefundRequestedByNotNil() predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotNull(FieldRefundRequestedBy))
+}
+
+// RefundRequestedByEqualFold applies the EqualFold predicate on the "refund_requested_by" field.
+func RefundRequestedByEqualFold(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEqualFold(FieldRefundRequestedBy, v))
+}
+
+// RefundRequestedByContainsFold applies the ContainsFold predicate on the "refund_requested_by" field.
+func RefundRequestedByContainsFold(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldContainsFold(FieldRefundRequestedBy, v))
+}
+
+// ExpiresAtEQ applies the EQ predicate on the "expires_at" field.
+func ExpiresAtEQ(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldExpiresAt, v))
+}
+
+// ExpiresAtNEQ applies the NEQ predicate on the "expires_at" field.
+func ExpiresAtNEQ(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNEQ(FieldExpiresAt, v))
+}
+
+// ExpiresAtIn applies the In predicate on the "expires_at" field.
+func ExpiresAtIn(vs ...time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIn(FieldExpiresAt, vs...))
+}
+
+// ExpiresAtNotIn applies the NotIn predicate on the "expires_at" field.
+func ExpiresAtNotIn(vs ...time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotIn(FieldExpiresAt, vs...))
+}
+
+// ExpiresAtGT applies the GT predicate on the "expires_at" field.
+func ExpiresAtGT(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGT(FieldExpiresAt, v))
+}
+
+// ExpiresAtGTE applies the GTE predicate on the "expires_at" field.
+func ExpiresAtGTE(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGTE(FieldExpiresAt, v))
+}
+
+// ExpiresAtLT applies the LT predicate on the "expires_at" field.
+func ExpiresAtLT(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLT(FieldExpiresAt, v))
+}
+
+// ExpiresAtLTE applies the LTE predicate on the "expires_at" field.
+func ExpiresAtLTE(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLTE(FieldExpiresAt, v))
+}
+
+// PaidAtEQ applies the EQ predicate on the "paid_at" field.
+func PaidAtEQ(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldPaidAt, v))
+}
+
+// PaidAtNEQ applies the NEQ predicate on the "paid_at" field.
+func PaidAtNEQ(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNEQ(FieldPaidAt, v))
+}
+
+// PaidAtIn applies the In predicate on the "paid_at" field.
+func PaidAtIn(vs ...time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIn(FieldPaidAt, vs...))
+}
+
+// PaidAtNotIn applies the NotIn predicate on the "paid_at" field.
+func PaidAtNotIn(vs ...time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotIn(FieldPaidAt, vs...))
+}
+
+// PaidAtGT applies the GT predicate on the "paid_at" field.
+func PaidAtGT(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGT(FieldPaidAt, v))
+}
+
+// PaidAtGTE applies the GTE predicate on the "paid_at" field.
+func PaidAtGTE(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGTE(FieldPaidAt, v))
+}
+
+// PaidAtLT applies the LT predicate on the "paid_at" field.
+func PaidAtLT(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLT(FieldPaidAt, v))
+}
+
+// PaidAtLTE applies the LTE predicate on the "paid_at" field.
+func PaidAtLTE(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLTE(FieldPaidAt, v))
+}
+
+// PaidAtIsNil applies the IsNil predicate on the "paid_at" field.
+func PaidAtIsNil() predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIsNull(FieldPaidAt))
+}
+
+// PaidAtNotNil applies the NotNil predicate on the "paid_at" field.
+func PaidAtNotNil() predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotNull(FieldPaidAt))
+}
+
+// CompletedAtEQ applies the EQ predicate on the "completed_at" field.
+func CompletedAtEQ(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldCompletedAt, v))
+}
+
+// CompletedAtNEQ applies the NEQ predicate on the "completed_at" field.
+func CompletedAtNEQ(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNEQ(FieldCompletedAt, v))
+}
+
+// CompletedAtIn applies the In predicate on the "completed_at" field.
+func CompletedAtIn(vs ...time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIn(FieldCompletedAt, vs...))
+}
+
+// CompletedAtNotIn applies the NotIn predicate on the "completed_at" field.
+func CompletedAtNotIn(vs ...time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotIn(FieldCompletedAt, vs...))
+}
+
+// CompletedAtGT applies the GT predicate on the "completed_at" field.
+func CompletedAtGT(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGT(FieldCompletedAt, v))
+}
+
+// CompletedAtGTE applies the GTE predicate on the "completed_at" field.
+func CompletedAtGTE(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGTE(FieldCompletedAt, v))
+}
+
+// CompletedAtLT applies the LT predicate on the "completed_at" field.
+func CompletedAtLT(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLT(FieldCompletedAt, v))
+}
+
+// CompletedAtLTE applies the LTE predicate on the "completed_at" field.
+func CompletedAtLTE(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLTE(FieldCompletedAt, v))
+}
+
+// CompletedAtIsNil applies the IsNil predicate on the "completed_at" field.
+func CompletedAtIsNil() predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIsNull(FieldCompletedAt))
+}
+
+// CompletedAtNotNil applies the NotNil predicate on the "completed_at" field.
+func CompletedAtNotNil() predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotNull(FieldCompletedAt))
+}
+
+// FailedAtEQ applies the EQ predicate on the "failed_at" field.
+func FailedAtEQ(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldFailedAt, v))
+}
+
+// FailedAtNEQ applies the NEQ predicate on the "failed_at" field.
+func FailedAtNEQ(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNEQ(FieldFailedAt, v))
+}
+
+// FailedAtIn applies the In predicate on the "failed_at" field.
+func FailedAtIn(vs ...time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIn(FieldFailedAt, vs...))
+}
+
+// FailedAtNotIn applies the NotIn predicate on the "failed_at" field.
+func FailedAtNotIn(vs ...time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotIn(FieldFailedAt, vs...))
+}
+
+// FailedAtGT applies the GT predicate on the "failed_at" field.
+func FailedAtGT(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGT(FieldFailedAt, v))
+}
+
+// FailedAtGTE applies the GTE predicate on the "failed_at" field.
+func FailedAtGTE(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGTE(FieldFailedAt, v))
+}
+
+// FailedAtLT applies the LT predicate on the "failed_at" field.
+func FailedAtLT(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLT(FieldFailedAt, v))
+}
+
+// FailedAtLTE applies the LTE predicate on the "failed_at" field.
+func FailedAtLTE(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLTE(FieldFailedAt, v))
+}
+
+// FailedAtIsNil applies the IsNil predicate on the "failed_at" field.
+func FailedAtIsNil() predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIsNull(FieldFailedAt))
+}
+
+// FailedAtNotNil applies the NotNil predicate on the "failed_at" field.
+func FailedAtNotNil() predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotNull(FieldFailedAt))
+}
+
+// FailedReasonEQ applies the EQ predicate on the "failed_reason" field.
+func FailedReasonEQ(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldFailedReason, v))
+}
+
+// FailedReasonNEQ applies the NEQ predicate on the "failed_reason" field.
+func FailedReasonNEQ(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNEQ(FieldFailedReason, v))
+}
+
+// FailedReasonIn applies the In predicate on the "failed_reason" field.
+func FailedReasonIn(vs ...string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIn(FieldFailedReason, vs...))
+}
+
+// FailedReasonNotIn applies the NotIn predicate on the "failed_reason" field.
+func FailedReasonNotIn(vs ...string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotIn(FieldFailedReason, vs...))
+}
+
+// FailedReasonGT applies the GT predicate on the "failed_reason" field.
+func FailedReasonGT(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGT(FieldFailedReason, v))
+}
+
+// FailedReasonGTE applies the GTE predicate on the "failed_reason" field.
+func FailedReasonGTE(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGTE(FieldFailedReason, v))
+}
+
+// FailedReasonLT applies the LT predicate on the "failed_reason" field.
+func FailedReasonLT(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLT(FieldFailedReason, v))
+}
+
+// FailedReasonLTE applies the LTE predicate on the "failed_reason" field.
+func FailedReasonLTE(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLTE(FieldFailedReason, v))
+}
+
+// FailedReasonContains applies the Contains predicate on the "failed_reason" field.
+func FailedReasonContains(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldContains(FieldFailedReason, v))
+}
+
+// FailedReasonHasPrefix applies the HasPrefix predicate on the "failed_reason" field.
+func FailedReasonHasPrefix(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldHasPrefix(FieldFailedReason, v))
+}
+
+// FailedReasonHasSuffix applies the HasSuffix predicate on the "failed_reason" field.
+func FailedReasonHasSuffix(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldHasSuffix(FieldFailedReason, v))
+}
+
+// FailedReasonIsNil applies the IsNil predicate on the "failed_reason" field.
+func FailedReasonIsNil() predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIsNull(FieldFailedReason))
+}
+
+// FailedReasonNotNil applies the NotNil predicate on the "failed_reason" field.
+func FailedReasonNotNil() predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotNull(FieldFailedReason))
+}
+
+// FailedReasonEqualFold applies the EqualFold predicate on the "failed_reason" field.
+func FailedReasonEqualFold(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEqualFold(FieldFailedReason, v))
+}
+
+// FailedReasonContainsFold applies the ContainsFold predicate on the "failed_reason" field.
+func FailedReasonContainsFold(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldContainsFold(FieldFailedReason, v))
+}
+
+// ClientIPEQ applies the EQ predicate on the "client_ip" field.
+func ClientIPEQ(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldClientIP, v))
+}
+
+// ClientIPNEQ applies the NEQ predicate on the "client_ip" field.
+func ClientIPNEQ(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNEQ(FieldClientIP, v))
+}
+
+// ClientIPIn applies the In predicate on the "client_ip" field.
+func ClientIPIn(vs ...string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIn(FieldClientIP, vs...))
+}
+
+// ClientIPNotIn applies the NotIn predicate on the "client_ip" field.
+func ClientIPNotIn(vs ...string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotIn(FieldClientIP, vs...))
+}
+
+// ClientIPGT applies the GT predicate on the "client_ip" field.
+func ClientIPGT(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGT(FieldClientIP, v))
+}
+
+// ClientIPGTE applies the GTE predicate on the "client_ip" field.
+func ClientIPGTE(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGTE(FieldClientIP, v))
+}
+
+// ClientIPLT applies the LT predicate on the "client_ip" field.
+func ClientIPLT(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLT(FieldClientIP, v))
+}
+
+// ClientIPLTE applies the LTE predicate on the "client_ip" field.
+func ClientIPLTE(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLTE(FieldClientIP, v))
+}
+
+// ClientIPContains applies the Contains predicate on the "client_ip" field.
+func ClientIPContains(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldContains(FieldClientIP, v))
+}
+
+// ClientIPHasPrefix applies the HasPrefix predicate on the "client_ip" field.
+func ClientIPHasPrefix(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldHasPrefix(FieldClientIP, v))
+}
+
+// ClientIPHasSuffix applies the HasSuffix predicate on the "client_ip" field.
+func ClientIPHasSuffix(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldHasSuffix(FieldClientIP, v))
+}
+
+// ClientIPEqualFold applies the EqualFold predicate on the "client_ip" field.
+func ClientIPEqualFold(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEqualFold(FieldClientIP, v))
+}
+
+// ClientIPContainsFold applies the ContainsFold predicate on the "client_ip" field.
+func ClientIPContainsFold(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldContainsFold(FieldClientIP, v))
+}
+
+// SrcHostEQ applies the EQ predicate on the "src_host" field.
+func SrcHostEQ(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldSrcHost, v))
+}
+
+// SrcHostNEQ applies the NEQ predicate on the "src_host" field.
+func SrcHostNEQ(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNEQ(FieldSrcHost, v))
+}
+
+// SrcHostIn applies the In predicate on the "src_host" field.
+func SrcHostIn(vs ...string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIn(FieldSrcHost, vs...))
+}
+
+// SrcHostNotIn applies the NotIn predicate on the "src_host" field.
+func SrcHostNotIn(vs ...string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotIn(FieldSrcHost, vs...))
+}
+
+// SrcHostGT applies the GT predicate on the "src_host" field.
+func SrcHostGT(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGT(FieldSrcHost, v))
+}
+
+// SrcHostGTE applies the GTE predicate on the "src_host" field.
+func SrcHostGTE(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGTE(FieldSrcHost, v))
+}
+
+// SrcHostLT applies the LT predicate on the "src_host" field.
+func SrcHostLT(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLT(FieldSrcHost, v))
+}
+
+// SrcHostLTE applies the LTE predicate on the "src_host" field.
+func SrcHostLTE(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLTE(FieldSrcHost, v))
+}
+
+// SrcHostContains applies the Contains predicate on the "src_host" field.
+func SrcHostContains(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldContains(FieldSrcHost, v))
+}
+
+// SrcHostHasPrefix applies the HasPrefix predicate on the "src_host" field.
+func SrcHostHasPrefix(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldHasPrefix(FieldSrcHost, v))
+}
+
+// SrcHostHasSuffix applies the HasSuffix predicate on the "src_host" field.
+func SrcHostHasSuffix(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldHasSuffix(FieldSrcHost, v))
+}
+
+// SrcHostEqualFold applies the EqualFold predicate on the "src_host" field.
+func SrcHostEqualFold(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEqualFold(FieldSrcHost, v))
+}
+
+// SrcHostContainsFold applies the ContainsFold predicate on the "src_host" field.
+func SrcHostContainsFold(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldContainsFold(FieldSrcHost, v))
+}
+
+// SrcURLEQ applies the EQ predicate on the "src_url" field.
+func SrcURLEQ(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldSrcURL, v))
+}
+
+// SrcURLNEQ applies the NEQ predicate on the "src_url" field.
+func SrcURLNEQ(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNEQ(FieldSrcURL, v))
+}
+
+// SrcURLIn applies the In predicate on the "src_url" field.
+func SrcURLIn(vs ...string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIn(FieldSrcURL, vs...))
+}
+
+// SrcURLNotIn applies the NotIn predicate on the "src_url" field.
+func SrcURLNotIn(vs ...string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotIn(FieldSrcURL, vs...))
+}
+
+// SrcURLGT applies the GT predicate on the "src_url" field.
+func SrcURLGT(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGT(FieldSrcURL, v))
+}
+
+// SrcURLGTE applies the GTE predicate on the "src_url" field.
+func SrcURLGTE(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGTE(FieldSrcURL, v))
+}
+
+// SrcURLLT applies the LT predicate on the "src_url" field.
+func SrcURLLT(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLT(FieldSrcURL, v))
+}
+
+// SrcURLLTE applies the LTE predicate on the "src_url" field.
+func SrcURLLTE(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLTE(FieldSrcURL, v))
+}
+
+// SrcURLContains applies the Contains predicate on the "src_url" field.
+func SrcURLContains(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldContains(FieldSrcURL, v))
+}
+
+// SrcURLHasPrefix applies the HasPrefix predicate on the "src_url" field.
+func SrcURLHasPrefix(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldHasPrefix(FieldSrcURL, v))
+}
+
+// SrcURLHasSuffix applies the HasSuffix predicate on the "src_url" field.
+func SrcURLHasSuffix(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldHasSuffix(FieldSrcURL, v))
+}
+
+// SrcURLIsNil applies the IsNil predicate on the "src_url" field.
+func SrcURLIsNil() predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIsNull(FieldSrcURL))
+}
+
+// SrcURLNotNil applies the NotNil predicate on the "src_url" field.
+func SrcURLNotNil() predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotNull(FieldSrcURL))
+}
+
+// SrcURLEqualFold applies the EqualFold predicate on the "src_url" field.
+func SrcURLEqualFold(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEqualFold(FieldSrcURL, v))
+}
+
+// SrcURLContainsFold applies the ContainsFold predicate on the "src_url" field.
+func SrcURLContainsFold(v string) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldContainsFold(FieldSrcURL, v))
+}
+
+// CreatedAtEQ applies the EQ predicate on the "created_at" field.
+func CreatedAtEQ(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldCreatedAt, v))
+}
+
+// CreatedAtNEQ applies the NEQ predicate on the "created_at" field.
+func CreatedAtNEQ(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNEQ(FieldCreatedAt, v))
+}
+
+// CreatedAtIn applies the In predicate on the "created_at" field.
+func CreatedAtIn(vs ...time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIn(FieldCreatedAt, vs...))
+}
+
+// CreatedAtNotIn applies the NotIn predicate on the "created_at" field.
+func CreatedAtNotIn(vs ...time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotIn(FieldCreatedAt, vs...))
+}
+
+// CreatedAtGT applies the GT predicate on the "created_at" field.
+func CreatedAtGT(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGT(FieldCreatedAt, v))
+}
+
+// CreatedAtGTE applies the GTE predicate on the "created_at" field.
+func CreatedAtGTE(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGTE(FieldCreatedAt, v))
+}
+
+// CreatedAtLT applies the LT predicate on the "created_at" field.
+func CreatedAtLT(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLT(FieldCreatedAt, v))
+}
+
+// CreatedAtLTE applies the LTE predicate on the "created_at" field.
+func CreatedAtLTE(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLTE(FieldCreatedAt, v))
+}
+
+// UpdatedAtEQ applies the EQ predicate on the "updated_at" field.
+func UpdatedAtEQ(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldEQ(FieldUpdatedAt, v))
+}
+
+// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field.
+func UpdatedAtNEQ(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNEQ(FieldUpdatedAt, v))
+}
+
+// UpdatedAtIn applies the In predicate on the "updated_at" field.
+func UpdatedAtIn(vs ...time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldIn(FieldUpdatedAt, vs...))
+}
+
+// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field.
+func UpdatedAtNotIn(vs ...time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldNotIn(FieldUpdatedAt, vs...))
+}
+
+// UpdatedAtGT applies the GT predicate on the "updated_at" field.
+func UpdatedAtGT(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGT(FieldUpdatedAt, v))
+}
+
+// UpdatedAtGTE applies the GTE predicate on the "updated_at" field.
+func UpdatedAtGTE(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldGTE(FieldUpdatedAt, v))
+}
+
+// UpdatedAtLT applies the LT predicate on the "updated_at" field.
+func UpdatedAtLT(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLT(FieldUpdatedAt, v))
+}
+
+// UpdatedAtLTE applies the LTE predicate on the "updated_at" field.
+func UpdatedAtLTE(v time.Time) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.FieldLTE(FieldUpdatedAt, v))
+}
+
+// HasUser applies the HasEdge predicate on the "user" edge.
+func HasUser() predicate.PaymentOrder {
+ return predicate.PaymentOrder(func(s *sql.Selector) {
+ step := sqlgraph.NewStep(
+ sqlgraph.From(Table, FieldID),
+ sqlgraph.Edge(sqlgraph.M2O, true, UserTable, UserColumn),
+ )
+ sqlgraph.HasNeighbors(s, step)
+ })
+}
+
+// HasUserWith applies the HasEdge predicate on the "user" edge with a given conditions (other predicates).
+func HasUserWith(preds ...predicate.User) predicate.PaymentOrder {
+ return predicate.PaymentOrder(func(s *sql.Selector) {
+ step := newUserStep()
+ sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
+ for _, p := range preds {
+ p(s)
+ }
+ })
+ })
+}
+
+// And groups predicates with the AND operator between them.
+func And(predicates ...predicate.PaymentOrder) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.AndPredicates(predicates...))
+}
+
+// Or groups predicates with the OR operator between them.
+func Or(predicates ...predicate.PaymentOrder) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.OrPredicates(predicates...))
+}
+
+// Not applies the not operator on the given predicate.
+func Not(p predicate.PaymentOrder) predicate.PaymentOrder {
+ return predicate.PaymentOrder(sql.NotPredicates(p))
+}
diff --git a/backend/ent/paymentorder_create.go b/backend/ent/paymentorder_create.go
new file mode 100644
index 0000000000000000000000000000000000000000..030983390124b2840304ae5f021b6634ed3abdba
--- /dev/null
+++ b/backend/ent/paymentorder_create.go
@@ -0,0 +1,3109 @@
+// 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/paymentorder"
+ "github.com/Wei-Shaw/sub2api/ent/user"
+)
+
+// PaymentOrderCreate is the builder for creating a PaymentOrder entity.
+type PaymentOrderCreate struct {
+ config
+ mutation *PaymentOrderMutation
+ hooks []Hook
+ conflict []sql.ConflictOption
+}
+
+// SetUserID sets the "user_id" field.
+func (_c *PaymentOrderCreate) SetUserID(v int64) *PaymentOrderCreate {
+ _c.mutation.SetUserID(v)
+ return _c
+}
+
+// SetUserEmail sets the "user_email" field.
+func (_c *PaymentOrderCreate) SetUserEmail(v string) *PaymentOrderCreate {
+ _c.mutation.SetUserEmail(v)
+ return _c
+}
+
+// SetUserName sets the "user_name" field.
+func (_c *PaymentOrderCreate) SetUserName(v string) *PaymentOrderCreate {
+ _c.mutation.SetUserName(v)
+ return _c
+}
+
+// SetUserNotes sets the "user_notes" field.
+func (_c *PaymentOrderCreate) SetUserNotes(v string) *PaymentOrderCreate {
+ _c.mutation.SetUserNotes(v)
+ return _c
+}
+
+// SetNillableUserNotes sets the "user_notes" field if the given value is not nil.
+func (_c *PaymentOrderCreate) SetNillableUserNotes(v *string) *PaymentOrderCreate {
+ if v != nil {
+ _c.SetUserNotes(*v)
+ }
+ return _c
+}
+
+// SetAmount sets the "amount" field.
+func (_c *PaymentOrderCreate) SetAmount(v float64) *PaymentOrderCreate {
+ _c.mutation.SetAmount(v)
+ return _c
+}
+
+// SetPayAmount sets the "pay_amount" field.
+func (_c *PaymentOrderCreate) SetPayAmount(v float64) *PaymentOrderCreate {
+ _c.mutation.SetPayAmount(v)
+ return _c
+}
+
+// SetFeeRate sets the "fee_rate" field.
+func (_c *PaymentOrderCreate) SetFeeRate(v float64) *PaymentOrderCreate {
+ _c.mutation.SetFeeRate(v)
+ return _c
+}
+
+// SetNillableFeeRate sets the "fee_rate" field if the given value is not nil.
+func (_c *PaymentOrderCreate) SetNillableFeeRate(v *float64) *PaymentOrderCreate {
+ if v != nil {
+ _c.SetFeeRate(*v)
+ }
+ return _c
+}
+
+// SetRechargeCode sets the "recharge_code" field.
+func (_c *PaymentOrderCreate) SetRechargeCode(v string) *PaymentOrderCreate {
+ _c.mutation.SetRechargeCode(v)
+ return _c
+}
+
+// SetOutTradeNo sets the "out_trade_no" field.
+func (_c *PaymentOrderCreate) SetOutTradeNo(v string) *PaymentOrderCreate {
+ _c.mutation.SetOutTradeNo(v)
+ return _c
+}
+
+// SetNillableOutTradeNo sets the "out_trade_no" field if the given value is not nil.
+func (_c *PaymentOrderCreate) SetNillableOutTradeNo(v *string) *PaymentOrderCreate {
+ if v != nil {
+ _c.SetOutTradeNo(*v)
+ }
+ return _c
+}
+
+// SetPaymentType sets the "payment_type" field.
+func (_c *PaymentOrderCreate) SetPaymentType(v string) *PaymentOrderCreate {
+ _c.mutation.SetPaymentType(v)
+ return _c
+}
+
+// SetPaymentTradeNo sets the "payment_trade_no" field.
+func (_c *PaymentOrderCreate) SetPaymentTradeNo(v string) *PaymentOrderCreate {
+ _c.mutation.SetPaymentTradeNo(v)
+ return _c
+}
+
+// SetPayURL sets the "pay_url" field.
+func (_c *PaymentOrderCreate) SetPayURL(v string) *PaymentOrderCreate {
+ _c.mutation.SetPayURL(v)
+ return _c
+}
+
+// SetNillablePayURL sets the "pay_url" field if the given value is not nil.
+func (_c *PaymentOrderCreate) SetNillablePayURL(v *string) *PaymentOrderCreate {
+ if v != nil {
+ _c.SetPayURL(*v)
+ }
+ return _c
+}
+
+// SetQrCode sets the "qr_code" field.
+func (_c *PaymentOrderCreate) SetQrCode(v string) *PaymentOrderCreate {
+ _c.mutation.SetQrCode(v)
+ return _c
+}
+
+// SetNillableQrCode sets the "qr_code" field if the given value is not nil.
+func (_c *PaymentOrderCreate) SetNillableQrCode(v *string) *PaymentOrderCreate {
+ if v != nil {
+ _c.SetQrCode(*v)
+ }
+ return _c
+}
+
+// SetQrCodeImg sets the "qr_code_img" field.
+func (_c *PaymentOrderCreate) SetQrCodeImg(v string) *PaymentOrderCreate {
+ _c.mutation.SetQrCodeImg(v)
+ return _c
+}
+
+// SetNillableQrCodeImg sets the "qr_code_img" field if the given value is not nil.
+func (_c *PaymentOrderCreate) SetNillableQrCodeImg(v *string) *PaymentOrderCreate {
+ if v != nil {
+ _c.SetQrCodeImg(*v)
+ }
+ return _c
+}
+
+// SetOrderType sets the "order_type" field.
+func (_c *PaymentOrderCreate) SetOrderType(v string) *PaymentOrderCreate {
+ _c.mutation.SetOrderType(v)
+ return _c
+}
+
+// SetNillableOrderType sets the "order_type" field if the given value is not nil.
+func (_c *PaymentOrderCreate) SetNillableOrderType(v *string) *PaymentOrderCreate {
+ if v != nil {
+ _c.SetOrderType(*v)
+ }
+ return _c
+}
+
+// SetPlanID sets the "plan_id" field.
+func (_c *PaymentOrderCreate) SetPlanID(v int64) *PaymentOrderCreate {
+ _c.mutation.SetPlanID(v)
+ return _c
+}
+
+// SetNillablePlanID sets the "plan_id" field if the given value is not nil.
+func (_c *PaymentOrderCreate) SetNillablePlanID(v *int64) *PaymentOrderCreate {
+ if v != nil {
+ _c.SetPlanID(*v)
+ }
+ return _c
+}
+
+// SetSubscriptionGroupID sets the "subscription_group_id" field.
+func (_c *PaymentOrderCreate) SetSubscriptionGroupID(v int64) *PaymentOrderCreate {
+ _c.mutation.SetSubscriptionGroupID(v)
+ return _c
+}
+
+// SetNillableSubscriptionGroupID sets the "subscription_group_id" field if the given value is not nil.
+func (_c *PaymentOrderCreate) SetNillableSubscriptionGroupID(v *int64) *PaymentOrderCreate {
+ if v != nil {
+ _c.SetSubscriptionGroupID(*v)
+ }
+ return _c
+}
+
+// SetSubscriptionDays sets the "subscription_days" field.
+func (_c *PaymentOrderCreate) SetSubscriptionDays(v int) *PaymentOrderCreate {
+ _c.mutation.SetSubscriptionDays(v)
+ return _c
+}
+
+// SetNillableSubscriptionDays sets the "subscription_days" field if the given value is not nil.
+func (_c *PaymentOrderCreate) SetNillableSubscriptionDays(v *int) *PaymentOrderCreate {
+ if v != nil {
+ _c.SetSubscriptionDays(*v)
+ }
+ return _c
+}
+
+// SetProviderInstanceID sets the "provider_instance_id" field.
+func (_c *PaymentOrderCreate) SetProviderInstanceID(v string) *PaymentOrderCreate {
+ _c.mutation.SetProviderInstanceID(v)
+ return _c
+}
+
+// SetNillableProviderInstanceID sets the "provider_instance_id" field if the given value is not nil.
+func (_c *PaymentOrderCreate) SetNillableProviderInstanceID(v *string) *PaymentOrderCreate {
+ if v != nil {
+ _c.SetProviderInstanceID(*v)
+ }
+ return _c
+}
+
+// SetStatus sets the "status" field.
+func (_c *PaymentOrderCreate) SetStatus(v string) *PaymentOrderCreate {
+ _c.mutation.SetStatus(v)
+ return _c
+}
+
+// SetNillableStatus sets the "status" field if the given value is not nil.
+func (_c *PaymentOrderCreate) SetNillableStatus(v *string) *PaymentOrderCreate {
+ if v != nil {
+ _c.SetStatus(*v)
+ }
+ return _c
+}
+
+// SetRefundAmount sets the "refund_amount" field.
+func (_c *PaymentOrderCreate) SetRefundAmount(v float64) *PaymentOrderCreate {
+ _c.mutation.SetRefundAmount(v)
+ return _c
+}
+
+// SetNillableRefundAmount sets the "refund_amount" field if the given value is not nil.
+func (_c *PaymentOrderCreate) SetNillableRefundAmount(v *float64) *PaymentOrderCreate {
+ if v != nil {
+ _c.SetRefundAmount(*v)
+ }
+ return _c
+}
+
+// SetRefundReason sets the "refund_reason" field.
+func (_c *PaymentOrderCreate) SetRefundReason(v string) *PaymentOrderCreate {
+ _c.mutation.SetRefundReason(v)
+ return _c
+}
+
+// SetNillableRefundReason sets the "refund_reason" field if the given value is not nil.
+func (_c *PaymentOrderCreate) SetNillableRefundReason(v *string) *PaymentOrderCreate {
+ if v != nil {
+ _c.SetRefundReason(*v)
+ }
+ return _c
+}
+
+// SetRefundAt sets the "refund_at" field.
+func (_c *PaymentOrderCreate) SetRefundAt(v time.Time) *PaymentOrderCreate {
+ _c.mutation.SetRefundAt(v)
+ return _c
+}
+
+// SetNillableRefundAt sets the "refund_at" field if the given value is not nil.
+func (_c *PaymentOrderCreate) SetNillableRefundAt(v *time.Time) *PaymentOrderCreate {
+ if v != nil {
+ _c.SetRefundAt(*v)
+ }
+ return _c
+}
+
+// SetForceRefund sets the "force_refund" field.
+func (_c *PaymentOrderCreate) SetForceRefund(v bool) *PaymentOrderCreate {
+ _c.mutation.SetForceRefund(v)
+ return _c
+}
+
+// SetNillableForceRefund sets the "force_refund" field if the given value is not nil.
+func (_c *PaymentOrderCreate) SetNillableForceRefund(v *bool) *PaymentOrderCreate {
+ if v != nil {
+ _c.SetForceRefund(*v)
+ }
+ return _c
+}
+
+// SetRefundRequestedAt sets the "refund_requested_at" field.
+func (_c *PaymentOrderCreate) SetRefundRequestedAt(v time.Time) *PaymentOrderCreate {
+ _c.mutation.SetRefundRequestedAt(v)
+ return _c
+}
+
+// SetNillableRefundRequestedAt sets the "refund_requested_at" field if the given value is not nil.
+func (_c *PaymentOrderCreate) SetNillableRefundRequestedAt(v *time.Time) *PaymentOrderCreate {
+ if v != nil {
+ _c.SetRefundRequestedAt(*v)
+ }
+ return _c
+}
+
+// SetRefundRequestReason sets the "refund_request_reason" field.
+func (_c *PaymentOrderCreate) SetRefundRequestReason(v string) *PaymentOrderCreate {
+ _c.mutation.SetRefundRequestReason(v)
+ return _c
+}
+
+// SetNillableRefundRequestReason sets the "refund_request_reason" field if the given value is not nil.
+func (_c *PaymentOrderCreate) SetNillableRefundRequestReason(v *string) *PaymentOrderCreate {
+ if v != nil {
+ _c.SetRefundRequestReason(*v)
+ }
+ return _c
+}
+
+// SetRefundRequestedBy sets the "refund_requested_by" field.
+func (_c *PaymentOrderCreate) SetRefundRequestedBy(v string) *PaymentOrderCreate {
+ _c.mutation.SetRefundRequestedBy(v)
+ return _c
+}
+
+// SetNillableRefundRequestedBy sets the "refund_requested_by" field if the given value is not nil.
+func (_c *PaymentOrderCreate) SetNillableRefundRequestedBy(v *string) *PaymentOrderCreate {
+ if v != nil {
+ _c.SetRefundRequestedBy(*v)
+ }
+ return _c
+}
+
+// SetExpiresAt sets the "expires_at" field.
+func (_c *PaymentOrderCreate) SetExpiresAt(v time.Time) *PaymentOrderCreate {
+ _c.mutation.SetExpiresAt(v)
+ return _c
+}
+
+// SetPaidAt sets the "paid_at" field.
+func (_c *PaymentOrderCreate) SetPaidAt(v time.Time) *PaymentOrderCreate {
+ _c.mutation.SetPaidAt(v)
+ return _c
+}
+
+// SetNillablePaidAt sets the "paid_at" field if the given value is not nil.
+func (_c *PaymentOrderCreate) SetNillablePaidAt(v *time.Time) *PaymentOrderCreate {
+ if v != nil {
+ _c.SetPaidAt(*v)
+ }
+ return _c
+}
+
+// SetCompletedAt sets the "completed_at" field.
+func (_c *PaymentOrderCreate) SetCompletedAt(v time.Time) *PaymentOrderCreate {
+ _c.mutation.SetCompletedAt(v)
+ return _c
+}
+
+// SetNillableCompletedAt sets the "completed_at" field if the given value is not nil.
+func (_c *PaymentOrderCreate) SetNillableCompletedAt(v *time.Time) *PaymentOrderCreate {
+ if v != nil {
+ _c.SetCompletedAt(*v)
+ }
+ return _c
+}
+
+// SetFailedAt sets the "failed_at" field.
+func (_c *PaymentOrderCreate) SetFailedAt(v time.Time) *PaymentOrderCreate {
+ _c.mutation.SetFailedAt(v)
+ return _c
+}
+
+// SetNillableFailedAt sets the "failed_at" field if the given value is not nil.
+func (_c *PaymentOrderCreate) SetNillableFailedAt(v *time.Time) *PaymentOrderCreate {
+ if v != nil {
+ _c.SetFailedAt(*v)
+ }
+ return _c
+}
+
+// SetFailedReason sets the "failed_reason" field.
+func (_c *PaymentOrderCreate) SetFailedReason(v string) *PaymentOrderCreate {
+ _c.mutation.SetFailedReason(v)
+ return _c
+}
+
+// SetNillableFailedReason sets the "failed_reason" field if the given value is not nil.
+func (_c *PaymentOrderCreate) SetNillableFailedReason(v *string) *PaymentOrderCreate {
+ if v != nil {
+ _c.SetFailedReason(*v)
+ }
+ return _c
+}
+
+// SetClientIP sets the "client_ip" field.
+func (_c *PaymentOrderCreate) SetClientIP(v string) *PaymentOrderCreate {
+ _c.mutation.SetClientIP(v)
+ return _c
+}
+
+// SetSrcHost sets the "src_host" field.
+func (_c *PaymentOrderCreate) SetSrcHost(v string) *PaymentOrderCreate {
+ _c.mutation.SetSrcHost(v)
+ return _c
+}
+
+// SetSrcURL sets the "src_url" field.
+func (_c *PaymentOrderCreate) SetSrcURL(v string) *PaymentOrderCreate {
+ _c.mutation.SetSrcURL(v)
+ return _c
+}
+
+// SetNillableSrcURL sets the "src_url" field if the given value is not nil.
+func (_c *PaymentOrderCreate) SetNillableSrcURL(v *string) *PaymentOrderCreate {
+ if v != nil {
+ _c.SetSrcURL(*v)
+ }
+ return _c
+}
+
+// SetCreatedAt sets the "created_at" field.
+func (_c *PaymentOrderCreate) SetCreatedAt(v time.Time) *PaymentOrderCreate {
+ _c.mutation.SetCreatedAt(v)
+ return _c
+}
+
+// SetNillableCreatedAt sets the "created_at" field if the given value is not nil.
+func (_c *PaymentOrderCreate) SetNillableCreatedAt(v *time.Time) *PaymentOrderCreate {
+ if v != nil {
+ _c.SetCreatedAt(*v)
+ }
+ return _c
+}
+
+// SetUpdatedAt sets the "updated_at" field.
+func (_c *PaymentOrderCreate) SetUpdatedAt(v time.Time) *PaymentOrderCreate {
+ _c.mutation.SetUpdatedAt(v)
+ return _c
+}
+
+// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil.
+func (_c *PaymentOrderCreate) SetNillableUpdatedAt(v *time.Time) *PaymentOrderCreate {
+ if v != nil {
+ _c.SetUpdatedAt(*v)
+ }
+ return _c
+}
+
+// SetUser sets the "user" edge to the User entity.
+func (_c *PaymentOrderCreate) SetUser(v *User) *PaymentOrderCreate {
+ return _c.SetUserID(v.ID)
+}
+
+// Mutation returns the PaymentOrderMutation object of the builder.
+func (_c *PaymentOrderCreate) Mutation() *PaymentOrderMutation {
+ return _c.mutation
+}
+
+// Save creates the PaymentOrder in the database.
+func (_c *PaymentOrderCreate) Save(ctx context.Context) (*PaymentOrder, error) {
+ _c.defaults()
+ return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks)
+}
+
+// SaveX calls Save and panics if Save returns an error.
+func (_c *PaymentOrderCreate) SaveX(ctx context.Context) *PaymentOrder {
+ v, err := _c.Save(ctx)
+ if err != nil {
+ panic(err)
+ }
+ return v
+}
+
+// Exec executes the query.
+func (_c *PaymentOrderCreate) Exec(ctx context.Context) error {
+ _, err := _c.Save(ctx)
+ return err
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (_c *PaymentOrderCreate) 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 *PaymentOrderCreate) defaults() {
+ if _, ok := _c.mutation.FeeRate(); !ok {
+ v := paymentorder.DefaultFeeRate
+ _c.mutation.SetFeeRate(v)
+ }
+ if _, ok := _c.mutation.OutTradeNo(); !ok {
+ v := paymentorder.DefaultOutTradeNo
+ _c.mutation.SetOutTradeNo(v)
+ }
+ if _, ok := _c.mutation.OrderType(); !ok {
+ v := paymentorder.DefaultOrderType
+ _c.mutation.SetOrderType(v)
+ }
+ if _, ok := _c.mutation.Status(); !ok {
+ v := paymentorder.DefaultStatus
+ _c.mutation.SetStatus(v)
+ }
+ if _, ok := _c.mutation.RefundAmount(); !ok {
+ v := paymentorder.DefaultRefundAmount
+ _c.mutation.SetRefundAmount(v)
+ }
+ if _, ok := _c.mutation.ForceRefund(); !ok {
+ v := paymentorder.DefaultForceRefund
+ _c.mutation.SetForceRefund(v)
+ }
+ if _, ok := _c.mutation.CreatedAt(); !ok {
+ v := paymentorder.DefaultCreatedAt()
+ _c.mutation.SetCreatedAt(v)
+ }
+ if _, ok := _c.mutation.UpdatedAt(); !ok {
+ v := paymentorder.DefaultUpdatedAt()
+ _c.mutation.SetUpdatedAt(v)
+ }
+}
+
+// check runs all checks and user-defined validators on the builder.
+func (_c *PaymentOrderCreate) check() error {
+ if _, ok := _c.mutation.UserID(); !ok {
+ return &ValidationError{Name: "user_id", err: errors.New(`ent: missing required field "PaymentOrder.user_id"`)}
+ }
+ if _, ok := _c.mutation.UserEmail(); !ok {
+ return &ValidationError{Name: "user_email", err: errors.New(`ent: missing required field "PaymentOrder.user_email"`)}
+ }
+ if v, ok := _c.mutation.UserEmail(); ok {
+ if err := paymentorder.UserEmailValidator(v); err != nil {
+ return &ValidationError{Name: "user_email", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.user_email": %w`, err)}
+ }
+ }
+ if _, ok := _c.mutation.UserName(); !ok {
+ return &ValidationError{Name: "user_name", err: errors.New(`ent: missing required field "PaymentOrder.user_name"`)}
+ }
+ if v, ok := _c.mutation.UserName(); ok {
+ if err := paymentorder.UserNameValidator(v); err != nil {
+ return &ValidationError{Name: "user_name", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.user_name": %w`, err)}
+ }
+ }
+ if _, ok := _c.mutation.Amount(); !ok {
+ return &ValidationError{Name: "amount", err: errors.New(`ent: missing required field "PaymentOrder.amount"`)}
+ }
+ if _, ok := _c.mutation.PayAmount(); !ok {
+ return &ValidationError{Name: "pay_amount", err: errors.New(`ent: missing required field "PaymentOrder.pay_amount"`)}
+ }
+ if _, ok := _c.mutation.FeeRate(); !ok {
+ return &ValidationError{Name: "fee_rate", err: errors.New(`ent: missing required field "PaymentOrder.fee_rate"`)}
+ }
+ if _, ok := _c.mutation.RechargeCode(); !ok {
+ return &ValidationError{Name: "recharge_code", err: errors.New(`ent: missing required field "PaymentOrder.recharge_code"`)}
+ }
+ if v, ok := _c.mutation.RechargeCode(); ok {
+ if err := paymentorder.RechargeCodeValidator(v); err != nil {
+ return &ValidationError{Name: "recharge_code", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.recharge_code": %w`, err)}
+ }
+ }
+ if _, ok := _c.mutation.OutTradeNo(); !ok {
+ return &ValidationError{Name: "out_trade_no", err: errors.New(`ent: missing required field "PaymentOrder.out_trade_no"`)}
+ }
+ if v, ok := _c.mutation.OutTradeNo(); ok {
+ if err := paymentorder.OutTradeNoValidator(v); err != nil {
+ return &ValidationError{Name: "out_trade_no", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.out_trade_no": %w`, err)}
+ }
+ }
+ if _, ok := _c.mutation.PaymentType(); !ok {
+ return &ValidationError{Name: "payment_type", err: errors.New(`ent: missing required field "PaymentOrder.payment_type"`)}
+ }
+ if v, ok := _c.mutation.PaymentType(); ok {
+ if err := paymentorder.PaymentTypeValidator(v); err != nil {
+ return &ValidationError{Name: "payment_type", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.payment_type": %w`, err)}
+ }
+ }
+ if _, ok := _c.mutation.PaymentTradeNo(); !ok {
+ return &ValidationError{Name: "payment_trade_no", err: errors.New(`ent: missing required field "PaymentOrder.payment_trade_no"`)}
+ }
+ if v, ok := _c.mutation.PaymentTradeNo(); ok {
+ if err := paymentorder.PaymentTradeNoValidator(v); err != nil {
+ return &ValidationError{Name: "payment_trade_no", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.payment_trade_no": %w`, err)}
+ }
+ }
+ if _, ok := _c.mutation.OrderType(); !ok {
+ return &ValidationError{Name: "order_type", err: errors.New(`ent: missing required field "PaymentOrder.order_type"`)}
+ }
+ if v, ok := _c.mutation.OrderType(); ok {
+ if err := paymentorder.OrderTypeValidator(v); err != nil {
+ return &ValidationError{Name: "order_type", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.order_type": %w`, err)}
+ }
+ }
+ if v, ok := _c.mutation.ProviderInstanceID(); ok {
+ if err := paymentorder.ProviderInstanceIDValidator(v); err != nil {
+ return &ValidationError{Name: "provider_instance_id", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.provider_instance_id": %w`, err)}
+ }
+ }
+ if _, ok := _c.mutation.Status(); !ok {
+ return &ValidationError{Name: "status", err: errors.New(`ent: missing required field "PaymentOrder.status"`)}
+ }
+ if v, ok := _c.mutation.Status(); ok {
+ if err := paymentorder.StatusValidator(v); err != nil {
+ return &ValidationError{Name: "status", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.status": %w`, err)}
+ }
+ }
+ if _, ok := _c.mutation.RefundAmount(); !ok {
+ return &ValidationError{Name: "refund_amount", err: errors.New(`ent: missing required field "PaymentOrder.refund_amount"`)}
+ }
+ if _, ok := _c.mutation.ForceRefund(); !ok {
+ return &ValidationError{Name: "force_refund", err: errors.New(`ent: missing required field "PaymentOrder.force_refund"`)}
+ }
+ if v, ok := _c.mutation.RefundRequestedBy(); ok {
+ if err := paymentorder.RefundRequestedByValidator(v); err != nil {
+ return &ValidationError{Name: "refund_requested_by", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.refund_requested_by": %w`, err)}
+ }
+ }
+ if _, ok := _c.mutation.ExpiresAt(); !ok {
+ return &ValidationError{Name: "expires_at", err: errors.New(`ent: missing required field "PaymentOrder.expires_at"`)}
+ }
+ if _, ok := _c.mutation.ClientIP(); !ok {
+ return &ValidationError{Name: "client_ip", err: errors.New(`ent: missing required field "PaymentOrder.client_ip"`)}
+ }
+ if v, ok := _c.mutation.ClientIP(); ok {
+ if err := paymentorder.ClientIPValidator(v); err != nil {
+ return &ValidationError{Name: "client_ip", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.client_ip": %w`, err)}
+ }
+ }
+ if _, ok := _c.mutation.SrcHost(); !ok {
+ return &ValidationError{Name: "src_host", err: errors.New(`ent: missing required field "PaymentOrder.src_host"`)}
+ }
+ if v, ok := _c.mutation.SrcHost(); ok {
+ if err := paymentorder.SrcHostValidator(v); err != nil {
+ return &ValidationError{Name: "src_host", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.src_host": %w`, err)}
+ }
+ }
+ if _, ok := _c.mutation.CreatedAt(); !ok {
+ return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "PaymentOrder.created_at"`)}
+ }
+ if _, ok := _c.mutation.UpdatedAt(); !ok {
+ return &ValidationError{Name: "updated_at", err: errors.New(`ent: missing required field "PaymentOrder.updated_at"`)}
+ }
+ if len(_c.mutation.UserIDs()) == 0 {
+ return &ValidationError{Name: "user", err: errors.New(`ent: missing required edge "PaymentOrder.user"`)}
+ }
+ return nil
+}
+
+func (_c *PaymentOrderCreate) sqlSave(ctx context.Context) (*PaymentOrder, 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 *PaymentOrderCreate) createSpec() (*PaymentOrder, *sqlgraph.CreateSpec) {
+ var (
+ _node = &PaymentOrder{config: _c.config}
+ _spec = sqlgraph.NewCreateSpec(paymentorder.Table, sqlgraph.NewFieldSpec(paymentorder.FieldID, field.TypeInt64))
+ )
+ _spec.OnConflict = _c.conflict
+ if value, ok := _c.mutation.UserEmail(); ok {
+ _spec.SetField(paymentorder.FieldUserEmail, field.TypeString, value)
+ _node.UserEmail = value
+ }
+ if value, ok := _c.mutation.UserName(); ok {
+ _spec.SetField(paymentorder.FieldUserName, field.TypeString, value)
+ _node.UserName = value
+ }
+ if value, ok := _c.mutation.UserNotes(); ok {
+ _spec.SetField(paymentorder.FieldUserNotes, field.TypeString, value)
+ _node.UserNotes = &value
+ }
+ if value, ok := _c.mutation.Amount(); ok {
+ _spec.SetField(paymentorder.FieldAmount, field.TypeFloat64, value)
+ _node.Amount = value
+ }
+ if value, ok := _c.mutation.PayAmount(); ok {
+ _spec.SetField(paymentorder.FieldPayAmount, field.TypeFloat64, value)
+ _node.PayAmount = value
+ }
+ if value, ok := _c.mutation.FeeRate(); ok {
+ _spec.SetField(paymentorder.FieldFeeRate, field.TypeFloat64, value)
+ _node.FeeRate = value
+ }
+ if value, ok := _c.mutation.RechargeCode(); ok {
+ _spec.SetField(paymentorder.FieldRechargeCode, field.TypeString, value)
+ _node.RechargeCode = value
+ }
+ if value, ok := _c.mutation.OutTradeNo(); ok {
+ _spec.SetField(paymentorder.FieldOutTradeNo, field.TypeString, value)
+ _node.OutTradeNo = value
+ }
+ if value, ok := _c.mutation.PaymentType(); ok {
+ _spec.SetField(paymentorder.FieldPaymentType, field.TypeString, value)
+ _node.PaymentType = value
+ }
+ if value, ok := _c.mutation.PaymentTradeNo(); ok {
+ _spec.SetField(paymentorder.FieldPaymentTradeNo, field.TypeString, value)
+ _node.PaymentTradeNo = value
+ }
+ if value, ok := _c.mutation.PayURL(); ok {
+ _spec.SetField(paymentorder.FieldPayURL, field.TypeString, value)
+ _node.PayURL = &value
+ }
+ if value, ok := _c.mutation.QrCode(); ok {
+ _spec.SetField(paymentorder.FieldQrCode, field.TypeString, value)
+ _node.QrCode = &value
+ }
+ if value, ok := _c.mutation.QrCodeImg(); ok {
+ _spec.SetField(paymentorder.FieldQrCodeImg, field.TypeString, value)
+ _node.QrCodeImg = &value
+ }
+ if value, ok := _c.mutation.OrderType(); ok {
+ _spec.SetField(paymentorder.FieldOrderType, field.TypeString, value)
+ _node.OrderType = value
+ }
+ if value, ok := _c.mutation.PlanID(); ok {
+ _spec.SetField(paymentorder.FieldPlanID, field.TypeInt64, value)
+ _node.PlanID = &value
+ }
+ if value, ok := _c.mutation.SubscriptionGroupID(); ok {
+ _spec.SetField(paymentorder.FieldSubscriptionGroupID, field.TypeInt64, value)
+ _node.SubscriptionGroupID = &value
+ }
+ if value, ok := _c.mutation.SubscriptionDays(); ok {
+ _spec.SetField(paymentorder.FieldSubscriptionDays, field.TypeInt, value)
+ _node.SubscriptionDays = &value
+ }
+ if value, ok := _c.mutation.ProviderInstanceID(); ok {
+ _spec.SetField(paymentorder.FieldProviderInstanceID, field.TypeString, value)
+ _node.ProviderInstanceID = &value
+ }
+ if value, ok := _c.mutation.Status(); ok {
+ _spec.SetField(paymentorder.FieldStatus, field.TypeString, value)
+ _node.Status = value
+ }
+ if value, ok := _c.mutation.RefundAmount(); ok {
+ _spec.SetField(paymentorder.FieldRefundAmount, field.TypeFloat64, value)
+ _node.RefundAmount = value
+ }
+ if value, ok := _c.mutation.RefundReason(); ok {
+ _spec.SetField(paymentorder.FieldRefundReason, field.TypeString, value)
+ _node.RefundReason = &value
+ }
+ if value, ok := _c.mutation.RefundAt(); ok {
+ _spec.SetField(paymentorder.FieldRefundAt, field.TypeTime, value)
+ _node.RefundAt = &value
+ }
+ if value, ok := _c.mutation.ForceRefund(); ok {
+ _spec.SetField(paymentorder.FieldForceRefund, field.TypeBool, value)
+ _node.ForceRefund = value
+ }
+ if value, ok := _c.mutation.RefundRequestedAt(); ok {
+ _spec.SetField(paymentorder.FieldRefundRequestedAt, field.TypeTime, value)
+ _node.RefundRequestedAt = &value
+ }
+ if value, ok := _c.mutation.RefundRequestReason(); ok {
+ _spec.SetField(paymentorder.FieldRefundRequestReason, field.TypeString, value)
+ _node.RefundRequestReason = &value
+ }
+ if value, ok := _c.mutation.RefundRequestedBy(); ok {
+ _spec.SetField(paymentorder.FieldRefundRequestedBy, field.TypeString, value)
+ _node.RefundRequestedBy = &value
+ }
+ if value, ok := _c.mutation.ExpiresAt(); ok {
+ _spec.SetField(paymentorder.FieldExpiresAt, field.TypeTime, value)
+ _node.ExpiresAt = value
+ }
+ if value, ok := _c.mutation.PaidAt(); ok {
+ _spec.SetField(paymentorder.FieldPaidAt, field.TypeTime, value)
+ _node.PaidAt = &value
+ }
+ if value, ok := _c.mutation.CompletedAt(); ok {
+ _spec.SetField(paymentorder.FieldCompletedAt, field.TypeTime, value)
+ _node.CompletedAt = &value
+ }
+ if value, ok := _c.mutation.FailedAt(); ok {
+ _spec.SetField(paymentorder.FieldFailedAt, field.TypeTime, value)
+ _node.FailedAt = &value
+ }
+ if value, ok := _c.mutation.FailedReason(); ok {
+ _spec.SetField(paymentorder.FieldFailedReason, field.TypeString, value)
+ _node.FailedReason = &value
+ }
+ if value, ok := _c.mutation.ClientIP(); ok {
+ _spec.SetField(paymentorder.FieldClientIP, field.TypeString, value)
+ _node.ClientIP = value
+ }
+ if value, ok := _c.mutation.SrcHost(); ok {
+ _spec.SetField(paymentorder.FieldSrcHost, field.TypeString, value)
+ _node.SrcHost = value
+ }
+ if value, ok := _c.mutation.SrcURL(); ok {
+ _spec.SetField(paymentorder.FieldSrcURL, field.TypeString, value)
+ _node.SrcURL = &value
+ }
+ if value, ok := _c.mutation.CreatedAt(); ok {
+ _spec.SetField(paymentorder.FieldCreatedAt, field.TypeTime, value)
+ _node.CreatedAt = value
+ }
+ if value, ok := _c.mutation.UpdatedAt(); ok {
+ _spec.SetField(paymentorder.FieldUpdatedAt, field.TypeTime, value)
+ _node.UpdatedAt = value
+ }
+ if nodes := _c.mutation.UserIDs(); len(nodes) > 0 {
+ edge := &sqlgraph.EdgeSpec{
+ Rel: sqlgraph.M2O,
+ Inverse: true,
+ Table: paymentorder.UserTable,
+ Columns: []string{paymentorder.UserColumn},
+ Bidi: false,
+ Target: &sqlgraph.EdgeTarget{
+ IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt64),
+ },
+ }
+ for _, k := range nodes {
+ edge.Target.Nodes = append(edge.Target.Nodes, k)
+ }
+ _node.UserID = nodes[0]
+ _spec.Edges = append(_spec.Edges, edge)
+ }
+ return _node, _spec
+}
+
+// OnConflict allows configuring the `ON CONFLICT` / `ON DUPLICATE KEY` clause
+// of the `INSERT` statement. For example:
+//
+// client.PaymentOrder.Create().
+// SetUserID(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.PaymentOrderUpsert) {
+// SetUserID(v+v).
+// }).
+// Exec(ctx)
+func (_c *PaymentOrderCreate) OnConflict(opts ...sql.ConflictOption) *PaymentOrderUpsertOne {
+ _c.conflict = opts
+ return &PaymentOrderUpsertOne{
+ create: _c,
+ }
+}
+
+// OnConflictColumns calls `OnConflict` and configures the columns
+// as conflict target. Using this option is equivalent to using:
+//
+// client.PaymentOrder.Create().
+// OnConflict(sql.ConflictColumns(columns...)).
+// Exec(ctx)
+func (_c *PaymentOrderCreate) OnConflictColumns(columns ...string) *PaymentOrderUpsertOne {
+ _c.conflict = append(_c.conflict, sql.ConflictColumns(columns...))
+ return &PaymentOrderUpsertOne{
+ create: _c,
+ }
+}
+
+type (
+ // PaymentOrderUpsertOne is the builder for "upsert"-ing
+ // one PaymentOrder node.
+ PaymentOrderUpsertOne struct {
+ create *PaymentOrderCreate
+ }
+
+ // PaymentOrderUpsert is the "OnConflict" setter.
+ PaymentOrderUpsert struct {
+ *sql.UpdateSet
+ }
+)
+
+// SetUserID sets the "user_id" field.
+func (u *PaymentOrderUpsert) SetUserID(v int64) *PaymentOrderUpsert {
+ u.Set(paymentorder.FieldUserID, v)
+ return u
+}
+
+// UpdateUserID sets the "user_id" field to the value that was provided on create.
+func (u *PaymentOrderUpsert) UpdateUserID() *PaymentOrderUpsert {
+ u.SetExcluded(paymentorder.FieldUserID)
+ return u
+}
+
+// SetUserEmail sets the "user_email" field.
+func (u *PaymentOrderUpsert) SetUserEmail(v string) *PaymentOrderUpsert {
+ u.Set(paymentorder.FieldUserEmail, v)
+ return u
+}
+
+// UpdateUserEmail sets the "user_email" field to the value that was provided on create.
+func (u *PaymentOrderUpsert) UpdateUserEmail() *PaymentOrderUpsert {
+ u.SetExcluded(paymentorder.FieldUserEmail)
+ return u
+}
+
+// SetUserName sets the "user_name" field.
+func (u *PaymentOrderUpsert) SetUserName(v string) *PaymentOrderUpsert {
+ u.Set(paymentorder.FieldUserName, v)
+ return u
+}
+
+// UpdateUserName sets the "user_name" field to the value that was provided on create.
+func (u *PaymentOrderUpsert) UpdateUserName() *PaymentOrderUpsert {
+ u.SetExcluded(paymentorder.FieldUserName)
+ return u
+}
+
+// SetUserNotes sets the "user_notes" field.
+func (u *PaymentOrderUpsert) SetUserNotes(v string) *PaymentOrderUpsert {
+ u.Set(paymentorder.FieldUserNotes, v)
+ return u
+}
+
+// UpdateUserNotes sets the "user_notes" field to the value that was provided on create.
+func (u *PaymentOrderUpsert) UpdateUserNotes() *PaymentOrderUpsert {
+ u.SetExcluded(paymentorder.FieldUserNotes)
+ return u
+}
+
+// ClearUserNotes clears the value of the "user_notes" field.
+func (u *PaymentOrderUpsert) ClearUserNotes() *PaymentOrderUpsert {
+ u.SetNull(paymentorder.FieldUserNotes)
+ return u
+}
+
+// SetAmount sets the "amount" field.
+func (u *PaymentOrderUpsert) SetAmount(v float64) *PaymentOrderUpsert {
+ u.Set(paymentorder.FieldAmount, v)
+ return u
+}
+
+// UpdateAmount sets the "amount" field to the value that was provided on create.
+func (u *PaymentOrderUpsert) UpdateAmount() *PaymentOrderUpsert {
+ u.SetExcluded(paymentorder.FieldAmount)
+ return u
+}
+
+// AddAmount adds v to the "amount" field.
+func (u *PaymentOrderUpsert) AddAmount(v float64) *PaymentOrderUpsert {
+ u.Add(paymentorder.FieldAmount, v)
+ return u
+}
+
+// SetPayAmount sets the "pay_amount" field.
+func (u *PaymentOrderUpsert) SetPayAmount(v float64) *PaymentOrderUpsert {
+ u.Set(paymentorder.FieldPayAmount, v)
+ return u
+}
+
+// UpdatePayAmount sets the "pay_amount" field to the value that was provided on create.
+func (u *PaymentOrderUpsert) UpdatePayAmount() *PaymentOrderUpsert {
+ u.SetExcluded(paymentorder.FieldPayAmount)
+ return u
+}
+
+// AddPayAmount adds v to the "pay_amount" field.
+func (u *PaymentOrderUpsert) AddPayAmount(v float64) *PaymentOrderUpsert {
+ u.Add(paymentorder.FieldPayAmount, v)
+ return u
+}
+
+// SetFeeRate sets the "fee_rate" field.
+func (u *PaymentOrderUpsert) SetFeeRate(v float64) *PaymentOrderUpsert {
+ u.Set(paymentorder.FieldFeeRate, v)
+ return u
+}
+
+// UpdateFeeRate sets the "fee_rate" field to the value that was provided on create.
+func (u *PaymentOrderUpsert) UpdateFeeRate() *PaymentOrderUpsert {
+ u.SetExcluded(paymentorder.FieldFeeRate)
+ return u
+}
+
+// AddFeeRate adds v to the "fee_rate" field.
+func (u *PaymentOrderUpsert) AddFeeRate(v float64) *PaymentOrderUpsert {
+ u.Add(paymentorder.FieldFeeRate, v)
+ return u
+}
+
+// SetRechargeCode sets the "recharge_code" field.
+func (u *PaymentOrderUpsert) SetRechargeCode(v string) *PaymentOrderUpsert {
+ u.Set(paymentorder.FieldRechargeCode, v)
+ return u
+}
+
+// UpdateRechargeCode sets the "recharge_code" field to the value that was provided on create.
+func (u *PaymentOrderUpsert) UpdateRechargeCode() *PaymentOrderUpsert {
+ u.SetExcluded(paymentorder.FieldRechargeCode)
+ return u
+}
+
+// SetOutTradeNo sets the "out_trade_no" field.
+func (u *PaymentOrderUpsert) SetOutTradeNo(v string) *PaymentOrderUpsert {
+ u.Set(paymentorder.FieldOutTradeNo, v)
+ return u
+}
+
+// UpdateOutTradeNo sets the "out_trade_no" field to the value that was provided on create.
+func (u *PaymentOrderUpsert) UpdateOutTradeNo() *PaymentOrderUpsert {
+ u.SetExcluded(paymentorder.FieldOutTradeNo)
+ return u
+}
+
+// SetPaymentType sets the "payment_type" field.
+func (u *PaymentOrderUpsert) SetPaymentType(v string) *PaymentOrderUpsert {
+ u.Set(paymentorder.FieldPaymentType, v)
+ return u
+}
+
+// UpdatePaymentType sets the "payment_type" field to the value that was provided on create.
+func (u *PaymentOrderUpsert) UpdatePaymentType() *PaymentOrderUpsert {
+ u.SetExcluded(paymentorder.FieldPaymentType)
+ return u
+}
+
+// SetPaymentTradeNo sets the "payment_trade_no" field.
+func (u *PaymentOrderUpsert) SetPaymentTradeNo(v string) *PaymentOrderUpsert {
+ u.Set(paymentorder.FieldPaymentTradeNo, v)
+ return u
+}
+
+// UpdatePaymentTradeNo sets the "payment_trade_no" field to the value that was provided on create.
+func (u *PaymentOrderUpsert) UpdatePaymentTradeNo() *PaymentOrderUpsert {
+ u.SetExcluded(paymentorder.FieldPaymentTradeNo)
+ return u
+}
+
+// SetPayURL sets the "pay_url" field.
+func (u *PaymentOrderUpsert) SetPayURL(v string) *PaymentOrderUpsert {
+ u.Set(paymentorder.FieldPayURL, v)
+ return u
+}
+
+// UpdatePayURL sets the "pay_url" field to the value that was provided on create.
+func (u *PaymentOrderUpsert) UpdatePayURL() *PaymentOrderUpsert {
+ u.SetExcluded(paymentorder.FieldPayURL)
+ return u
+}
+
+// ClearPayURL clears the value of the "pay_url" field.
+func (u *PaymentOrderUpsert) ClearPayURL() *PaymentOrderUpsert {
+ u.SetNull(paymentorder.FieldPayURL)
+ return u
+}
+
+// SetQrCode sets the "qr_code" field.
+func (u *PaymentOrderUpsert) SetQrCode(v string) *PaymentOrderUpsert {
+ u.Set(paymentorder.FieldQrCode, v)
+ return u
+}
+
+// UpdateQrCode sets the "qr_code" field to the value that was provided on create.
+func (u *PaymentOrderUpsert) UpdateQrCode() *PaymentOrderUpsert {
+ u.SetExcluded(paymentorder.FieldQrCode)
+ return u
+}
+
+// ClearQrCode clears the value of the "qr_code" field.
+func (u *PaymentOrderUpsert) ClearQrCode() *PaymentOrderUpsert {
+ u.SetNull(paymentorder.FieldQrCode)
+ return u
+}
+
+// SetQrCodeImg sets the "qr_code_img" field.
+func (u *PaymentOrderUpsert) SetQrCodeImg(v string) *PaymentOrderUpsert {
+ u.Set(paymentorder.FieldQrCodeImg, v)
+ return u
+}
+
+// UpdateQrCodeImg sets the "qr_code_img" field to the value that was provided on create.
+func (u *PaymentOrderUpsert) UpdateQrCodeImg() *PaymentOrderUpsert {
+ u.SetExcluded(paymentorder.FieldQrCodeImg)
+ return u
+}
+
+// ClearQrCodeImg clears the value of the "qr_code_img" field.
+func (u *PaymentOrderUpsert) ClearQrCodeImg() *PaymentOrderUpsert {
+ u.SetNull(paymentorder.FieldQrCodeImg)
+ return u
+}
+
+// SetOrderType sets the "order_type" field.
+func (u *PaymentOrderUpsert) SetOrderType(v string) *PaymentOrderUpsert {
+ u.Set(paymentorder.FieldOrderType, v)
+ return u
+}
+
+// UpdateOrderType sets the "order_type" field to the value that was provided on create.
+func (u *PaymentOrderUpsert) UpdateOrderType() *PaymentOrderUpsert {
+ u.SetExcluded(paymentorder.FieldOrderType)
+ return u
+}
+
+// SetPlanID sets the "plan_id" field.
+func (u *PaymentOrderUpsert) SetPlanID(v int64) *PaymentOrderUpsert {
+ u.Set(paymentorder.FieldPlanID, v)
+ return u
+}
+
+// UpdatePlanID sets the "plan_id" field to the value that was provided on create.
+func (u *PaymentOrderUpsert) UpdatePlanID() *PaymentOrderUpsert {
+ u.SetExcluded(paymentorder.FieldPlanID)
+ return u
+}
+
+// AddPlanID adds v to the "plan_id" field.
+func (u *PaymentOrderUpsert) AddPlanID(v int64) *PaymentOrderUpsert {
+ u.Add(paymentorder.FieldPlanID, v)
+ return u
+}
+
+// ClearPlanID clears the value of the "plan_id" field.
+func (u *PaymentOrderUpsert) ClearPlanID() *PaymentOrderUpsert {
+ u.SetNull(paymentorder.FieldPlanID)
+ return u
+}
+
+// SetSubscriptionGroupID sets the "subscription_group_id" field.
+func (u *PaymentOrderUpsert) SetSubscriptionGroupID(v int64) *PaymentOrderUpsert {
+ u.Set(paymentorder.FieldSubscriptionGroupID, v)
+ return u
+}
+
+// UpdateSubscriptionGroupID sets the "subscription_group_id" field to the value that was provided on create.
+func (u *PaymentOrderUpsert) UpdateSubscriptionGroupID() *PaymentOrderUpsert {
+ u.SetExcluded(paymentorder.FieldSubscriptionGroupID)
+ return u
+}
+
+// AddSubscriptionGroupID adds v to the "subscription_group_id" field.
+func (u *PaymentOrderUpsert) AddSubscriptionGroupID(v int64) *PaymentOrderUpsert {
+ u.Add(paymentorder.FieldSubscriptionGroupID, v)
+ return u
+}
+
+// ClearSubscriptionGroupID clears the value of the "subscription_group_id" field.
+func (u *PaymentOrderUpsert) ClearSubscriptionGroupID() *PaymentOrderUpsert {
+ u.SetNull(paymentorder.FieldSubscriptionGroupID)
+ return u
+}
+
+// SetSubscriptionDays sets the "subscription_days" field.
+func (u *PaymentOrderUpsert) SetSubscriptionDays(v int) *PaymentOrderUpsert {
+ u.Set(paymentorder.FieldSubscriptionDays, v)
+ return u
+}
+
+// UpdateSubscriptionDays sets the "subscription_days" field to the value that was provided on create.
+func (u *PaymentOrderUpsert) UpdateSubscriptionDays() *PaymentOrderUpsert {
+ u.SetExcluded(paymentorder.FieldSubscriptionDays)
+ return u
+}
+
+// AddSubscriptionDays adds v to the "subscription_days" field.
+func (u *PaymentOrderUpsert) AddSubscriptionDays(v int) *PaymentOrderUpsert {
+ u.Add(paymentorder.FieldSubscriptionDays, v)
+ return u
+}
+
+// ClearSubscriptionDays clears the value of the "subscription_days" field.
+func (u *PaymentOrderUpsert) ClearSubscriptionDays() *PaymentOrderUpsert {
+ u.SetNull(paymentorder.FieldSubscriptionDays)
+ return u
+}
+
+// SetProviderInstanceID sets the "provider_instance_id" field.
+func (u *PaymentOrderUpsert) SetProviderInstanceID(v string) *PaymentOrderUpsert {
+ u.Set(paymentorder.FieldProviderInstanceID, v)
+ return u
+}
+
+// UpdateProviderInstanceID sets the "provider_instance_id" field to the value that was provided on create.
+func (u *PaymentOrderUpsert) UpdateProviderInstanceID() *PaymentOrderUpsert {
+ u.SetExcluded(paymentorder.FieldProviderInstanceID)
+ return u
+}
+
+// ClearProviderInstanceID clears the value of the "provider_instance_id" field.
+func (u *PaymentOrderUpsert) ClearProviderInstanceID() *PaymentOrderUpsert {
+ u.SetNull(paymentorder.FieldProviderInstanceID)
+ return u
+}
+
+// SetStatus sets the "status" field.
+func (u *PaymentOrderUpsert) SetStatus(v string) *PaymentOrderUpsert {
+ u.Set(paymentorder.FieldStatus, v)
+ return u
+}
+
+// UpdateStatus sets the "status" field to the value that was provided on create.
+func (u *PaymentOrderUpsert) UpdateStatus() *PaymentOrderUpsert {
+ u.SetExcluded(paymentorder.FieldStatus)
+ return u
+}
+
+// SetRefundAmount sets the "refund_amount" field.
+func (u *PaymentOrderUpsert) SetRefundAmount(v float64) *PaymentOrderUpsert {
+ u.Set(paymentorder.FieldRefundAmount, v)
+ return u
+}
+
+// UpdateRefundAmount sets the "refund_amount" field to the value that was provided on create.
+func (u *PaymentOrderUpsert) UpdateRefundAmount() *PaymentOrderUpsert {
+ u.SetExcluded(paymentorder.FieldRefundAmount)
+ return u
+}
+
+// AddRefundAmount adds v to the "refund_amount" field.
+func (u *PaymentOrderUpsert) AddRefundAmount(v float64) *PaymentOrderUpsert {
+ u.Add(paymentorder.FieldRefundAmount, v)
+ return u
+}
+
+// SetRefundReason sets the "refund_reason" field.
+func (u *PaymentOrderUpsert) SetRefundReason(v string) *PaymentOrderUpsert {
+ u.Set(paymentorder.FieldRefundReason, v)
+ return u
+}
+
+// UpdateRefundReason sets the "refund_reason" field to the value that was provided on create.
+func (u *PaymentOrderUpsert) UpdateRefundReason() *PaymentOrderUpsert {
+ u.SetExcluded(paymentorder.FieldRefundReason)
+ return u
+}
+
+// ClearRefundReason clears the value of the "refund_reason" field.
+func (u *PaymentOrderUpsert) ClearRefundReason() *PaymentOrderUpsert {
+ u.SetNull(paymentorder.FieldRefundReason)
+ return u
+}
+
+// SetRefundAt sets the "refund_at" field.
+func (u *PaymentOrderUpsert) SetRefundAt(v time.Time) *PaymentOrderUpsert {
+ u.Set(paymentorder.FieldRefundAt, v)
+ return u
+}
+
+// UpdateRefundAt sets the "refund_at" field to the value that was provided on create.
+func (u *PaymentOrderUpsert) UpdateRefundAt() *PaymentOrderUpsert {
+ u.SetExcluded(paymentorder.FieldRefundAt)
+ return u
+}
+
+// ClearRefundAt clears the value of the "refund_at" field.
+func (u *PaymentOrderUpsert) ClearRefundAt() *PaymentOrderUpsert {
+ u.SetNull(paymentorder.FieldRefundAt)
+ return u
+}
+
+// SetForceRefund sets the "force_refund" field.
+func (u *PaymentOrderUpsert) SetForceRefund(v bool) *PaymentOrderUpsert {
+ u.Set(paymentorder.FieldForceRefund, v)
+ return u
+}
+
+// UpdateForceRefund sets the "force_refund" field to the value that was provided on create.
+func (u *PaymentOrderUpsert) UpdateForceRefund() *PaymentOrderUpsert {
+ u.SetExcluded(paymentorder.FieldForceRefund)
+ return u
+}
+
+// SetRefundRequestedAt sets the "refund_requested_at" field.
+func (u *PaymentOrderUpsert) SetRefundRequestedAt(v time.Time) *PaymentOrderUpsert {
+ u.Set(paymentorder.FieldRefundRequestedAt, v)
+ return u
+}
+
+// UpdateRefundRequestedAt sets the "refund_requested_at" field to the value that was provided on create.
+func (u *PaymentOrderUpsert) UpdateRefundRequestedAt() *PaymentOrderUpsert {
+ u.SetExcluded(paymentorder.FieldRefundRequestedAt)
+ return u
+}
+
+// ClearRefundRequestedAt clears the value of the "refund_requested_at" field.
+func (u *PaymentOrderUpsert) ClearRefundRequestedAt() *PaymentOrderUpsert {
+ u.SetNull(paymentorder.FieldRefundRequestedAt)
+ return u
+}
+
+// SetRefundRequestReason sets the "refund_request_reason" field.
+func (u *PaymentOrderUpsert) SetRefundRequestReason(v string) *PaymentOrderUpsert {
+ u.Set(paymentorder.FieldRefundRequestReason, v)
+ return u
+}
+
+// UpdateRefundRequestReason sets the "refund_request_reason" field to the value that was provided on create.
+func (u *PaymentOrderUpsert) UpdateRefundRequestReason() *PaymentOrderUpsert {
+ u.SetExcluded(paymentorder.FieldRefundRequestReason)
+ return u
+}
+
+// ClearRefundRequestReason clears the value of the "refund_request_reason" field.
+func (u *PaymentOrderUpsert) ClearRefundRequestReason() *PaymentOrderUpsert {
+ u.SetNull(paymentorder.FieldRefundRequestReason)
+ return u
+}
+
+// SetRefundRequestedBy sets the "refund_requested_by" field.
+func (u *PaymentOrderUpsert) SetRefundRequestedBy(v string) *PaymentOrderUpsert {
+ u.Set(paymentorder.FieldRefundRequestedBy, v)
+ return u
+}
+
+// UpdateRefundRequestedBy sets the "refund_requested_by" field to the value that was provided on create.
+func (u *PaymentOrderUpsert) UpdateRefundRequestedBy() *PaymentOrderUpsert {
+ u.SetExcluded(paymentorder.FieldRefundRequestedBy)
+ return u
+}
+
+// ClearRefundRequestedBy clears the value of the "refund_requested_by" field.
+func (u *PaymentOrderUpsert) ClearRefundRequestedBy() *PaymentOrderUpsert {
+ u.SetNull(paymentorder.FieldRefundRequestedBy)
+ return u
+}
+
+// SetExpiresAt sets the "expires_at" field.
+func (u *PaymentOrderUpsert) SetExpiresAt(v time.Time) *PaymentOrderUpsert {
+ u.Set(paymentorder.FieldExpiresAt, v)
+ return u
+}
+
+// UpdateExpiresAt sets the "expires_at" field to the value that was provided on create.
+func (u *PaymentOrderUpsert) UpdateExpiresAt() *PaymentOrderUpsert {
+ u.SetExcluded(paymentorder.FieldExpiresAt)
+ return u
+}
+
+// SetPaidAt sets the "paid_at" field.
+func (u *PaymentOrderUpsert) SetPaidAt(v time.Time) *PaymentOrderUpsert {
+ u.Set(paymentorder.FieldPaidAt, v)
+ return u
+}
+
+// UpdatePaidAt sets the "paid_at" field to the value that was provided on create.
+func (u *PaymentOrderUpsert) UpdatePaidAt() *PaymentOrderUpsert {
+ u.SetExcluded(paymentorder.FieldPaidAt)
+ return u
+}
+
+// ClearPaidAt clears the value of the "paid_at" field.
+func (u *PaymentOrderUpsert) ClearPaidAt() *PaymentOrderUpsert {
+ u.SetNull(paymentorder.FieldPaidAt)
+ return u
+}
+
+// SetCompletedAt sets the "completed_at" field.
+func (u *PaymentOrderUpsert) SetCompletedAt(v time.Time) *PaymentOrderUpsert {
+ u.Set(paymentorder.FieldCompletedAt, v)
+ return u
+}
+
+// UpdateCompletedAt sets the "completed_at" field to the value that was provided on create.
+func (u *PaymentOrderUpsert) UpdateCompletedAt() *PaymentOrderUpsert {
+ u.SetExcluded(paymentorder.FieldCompletedAt)
+ return u
+}
+
+// ClearCompletedAt clears the value of the "completed_at" field.
+func (u *PaymentOrderUpsert) ClearCompletedAt() *PaymentOrderUpsert {
+ u.SetNull(paymentorder.FieldCompletedAt)
+ return u
+}
+
+// SetFailedAt sets the "failed_at" field.
+func (u *PaymentOrderUpsert) SetFailedAt(v time.Time) *PaymentOrderUpsert {
+ u.Set(paymentorder.FieldFailedAt, v)
+ return u
+}
+
+// UpdateFailedAt sets the "failed_at" field to the value that was provided on create.
+func (u *PaymentOrderUpsert) UpdateFailedAt() *PaymentOrderUpsert {
+ u.SetExcluded(paymentorder.FieldFailedAt)
+ return u
+}
+
+// ClearFailedAt clears the value of the "failed_at" field.
+func (u *PaymentOrderUpsert) ClearFailedAt() *PaymentOrderUpsert {
+ u.SetNull(paymentorder.FieldFailedAt)
+ return u
+}
+
+// SetFailedReason sets the "failed_reason" field.
+func (u *PaymentOrderUpsert) SetFailedReason(v string) *PaymentOrderUpsert {
+ u.Set(paymentorder.FieldFailedReason, v)
+ return u
+}
+
+// UpdateFailedReason sets the "failed_reason" field to the value that was provided on create.
+func (u *PaymentOrderUpsert) UpdateFailedReason() *PaymentOrderUpsert {
+ u.SetExcluded(paymentorder.FieldFailedReason)
+ return u
+}
+
+// ClearFailedReason clears the value of the "failed_reason" field.
+func (u *PaymentOrderUpsert) ClearFailedReason() *PaymentOrderUpsert {
+ u.SetNull(paymentorder.FieldFailedReason)
+ return u
+}
+
+// SetClientIP sets the "client_ip" field.
+func (u *PaymentOrderUpsert) SetClientIP(v string) *PaymentOrderUpsert {
+ u.Set(paymentorder.FieldClientIP, v)
+ return u
+}
+
+// UpdateClientIP sets the "client_ip" field to the value that was provided on create.
+func (u *PaymentOrderUpsert) UpdateClientIP() *PaymentOrderUpsert {
+ u.SetExcluded(paymentorder.FieldClientIP)
+ return u
+}
+
+// SetSrcHost sets the "src_host" field.
+func (u *PaymentOrderUpsert) SetSrcHost(v string) *PaymentOrderUpsert {
+ u.Set(paymentorder.FieldSrcHost, v)
+ return u
+}
+
+// UpdateSrcHost sets the "src_host" field to the value that was provided on create.
+func (u *PaymentOrderUpsert) UpdateSrcHost() *PaymentOrderUpsert {
+ u.SetExcluded(paymentorder.FieldSrcHost)
+ return u
+}
+
+// SetSrcURL sets the "src_url" field.
+func (u *PaymentOrderUpsert) SetSrcURL(v string) *PaymentOrderUpsert {
+ u.Set(paymentorder.FieldSrcURL, v)
+ return u
+}
+
+// UpdateSrcURL sets the "src_url" field to the value that was provided on create.
+func (u *PaymentOrderUpsert) UpdateSrcURL() *PaymentOrderUpsert {
+ u.SetExcluded(paymentorder.FieldSrcURL)
+ return u
+}
+
+// ClearSrcURL clears the value of the "src_url" field.
+func (u *PaymentOrderUpsert) ClearSrcURL() *PaymentOrderUpsert {
+ u.SetNull(paymentorder.FieldSrcURL)
+ return u
+}
+
+// SetUpdatedAt sets the "updated_at" field.
+func (u *PaymentOrderUpsert) SetUpdatedAt(v time.Time) *PaymentOrderUpsert {
+ u.Set(paymentorder.FieldUpdatedAt, v)
+ return u
+}
+
+// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create.
+func (u *PaymentOrderUpsert) UpdateUpdatedAt() *PaymentOrderUpsert {
+ u.SetExcluded(paymentorder.FieldUpdatedAt)
+ return u
+}
+
+// UpdateNewValues updates the mutable fields using the new values that were set on create.
+// Using this option is equivalent to using:
+//
+// client.PaymentOrder.Create().
+// OnConflict(
+// sql.ResolveWithNewValues(),
+// ).
+// Exec(ctx)
+func (u *PaymentOrderUpsertOne) UpdateNewValues() *PaymentOrderUpsertOne {
+ 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(paymentorder.FieldCreatedAt)
+ }
+ }))
+ return u
+}
+
+// Ignore sets each column to itself in case of conflict.
+// Using this option is equivalent to using:
+//
+// client.PaymentOrder.Create().
+// OnConflict(sql.ResolveWithIgnore()).
+// Exec(ctx)
+func (u *PaymentOrderUpsertOne) Ignore() *PaymentOrderUpsertOne {
+ 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 *PaymentOrderUpsertOne) DoNothing() *PaymentOrderUpsertOne {
+ u.create.conflict = append(u.create.conflict, sql.DoNothing())
+ return u
+}
+
+// Update allows overriding fields `UPDATE` values. See the PaymentOrderCreate.OnConflict
+// documentation for more info.
+func (u *PaymentOrderUpsertOne) Update(set func(*PaymentOrderUpsert)) *PaymentOrderUpsertOne {
+ u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) {
+ set(&PaymentOrderUpsert{UpdateSet: update})
+ }))
+ return u
+}
+
+// SetUserID sets the "user_id" field.
+func (u *PaymentOrderUpsertOne) SetUserID(v int64) *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetUserID(v)
+ })
+}
+
+// UpdateUserID sets the "user_id" field to the value that was provided on create.
+func (u *PaymentOrderUpsertOne) UpdateUserID() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateUserID()
+ })
+}
+
+// SetUserEmail sets the "user_email" field.
+func (u *PaymentOrderUpsertOne) SetUserEmail(v string) *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetUserEmail(v)
+ })
+}
+
+// UpdateUserEmail sets the "user_email" field to the value that was provided on create.
+func (u *PaymentOrderUpsertOne) UpdateUserEmail() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateUserEmail()
+ })
+}
+
+// SetUserName sets the "user_name" field.
+func (u *PaymentOrderUpsertOne) SetUserName(v string) *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetUserName(v)
+ })
+}
+
+// UpdateUserName sets the "user_name" field to the value that was provided on create.
+func (u *PaymentOrderUpsertOne) UpdateUserName() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateUserName()
+ })
+}
+
+// SetUserNotes sets the "user_notes" field.
+func (u *PaymentOrderUpsertOne) SetUserNotes(v string) *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetUserNotes(v)
+ })
+}
+
+// UpdateUserNotes sets the "user_notes" field to the value that was provided on create.
+func (u *PaymentOrderUpsertOne) UpdateUserNotes() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateUserNotes()
+ })
+}
+
+// ClearUserNotes clears the value of the "user_notes" field.
+func (u *PaymentOrderUpsertOne) ClearUserNotes() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.ClearUserNotes()
+ })
+}
+
+// SetAmount sets the "amount" field.
+func (u *PaymentOrderUpsertOne) SetAmount(v float64) *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetAmount(v)
+ })
+}
+
+// AddAmount adds v to the "amount" field.
+func (u *PaymentOrderUpsertOne) AddAmount(v float64) *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.AddAmount(v)
+ })
+}
+
+// UpdateAmount sets the "amount" field to the value that was provided on create.
+func (u *PaymentOrderUpsertOne) UpdateAmount() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateAmount()
+ })
+}
+
+// SetPayAmount sets the "pay_amount" field.
+func (u *PaymentOrderUpsertOne) SetPayAmount(v float64) *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetPayAmount(v)
+ })
+}
+
+// AddPayAmount adds v to the "pay_amount" field.
+func (u *PaymentOrderUpsertOne) AddPayAmount(v float64) *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.AddPayAmount(v)
+ })
+}
+
+// UpdatePayAmount sets the "pay_amount" field to the value that was provided on create.
+func (u *PaymentOrderUpsertOne) UpdatePayAmount() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdatePayAmount()
+ })
+}
+
+// SetFeeRate sets the "fee_rate" field.
+func (u *PaymentOrderUpsertOne) SetFeeRate(v float64) *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetFeeRate(v)
+ })
+}
+
+// AddFeeRate adds v to the "fee_rate" field.
+func (u *PaymentOrderUpsertOne) AddFeeRate(v float64) *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.AddFeeRate(v)
+ })
+}
+
+// UpdateFeeRate sets the "fee_rate" field to the value that was provided on create.
+func (u *PaymentOrderUpsertOne) UpdateFeeRate() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateFeeRate()
+ })
+}
+
+// SetRechargeCode sets the "recharge_code" field.
+func (u *PaymentOrderUpsertOne) SetRechargeCode(v string) *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetRechargeCode(v)
+ })
+}
+
+// UpdateRechargeCode sets the "recharge_code" field to the value that was provided on create.
+func (u *PaymentOrderUpsertOne) UpdateRechargeCode() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateRechargeCode()
+ })
+}
+
+// SetOutTradeNo sets the "out_trade_no" field.
+func (u *PaymentOrderUpsertOne) SetOutTradeNo(v string) *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetOutTradeNo(v)
+ })
+}
+
+// UpdateOutTradeNo sets the "out_trade_no" field to the value that was provided on create.
+func (u *PaymentOrderUpsertOne) UpdateOutTradeNo() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateOutTradeNo()
+ })
+}
+
+// SetPaymentType sets the "payment_type" field.
+func (u *PaymentOrderUpsertOne) SetPaymentType(v string) *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetPaymentType(v)
+ })
+}
+
+// UpdatePaymentType sets the "payment_type" field to the value that was provided on create.
+func (u *PaymentOrderUpsertOne) UpdatePaymentType() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdatePaymentType()
+ })
+}
+
+// SetPaymentTradeNo sets the "payment_trade_no" field.
+func (u *PaymentOrderUpsertOne) SetPaymentTradeNo(v string) *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetPaymentTradeNo(v)
+ })
+}
+
+// UpdatePaymentTradeNo sets the "payment_trade_no" field to the value that was provided on create.
+func (u *PaymentOrderUpsertOne) UpdatePaymentTradeNo() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdatePaymentTradeNo()
+ })
+}
+
+// SetPayURL sets the "pay_url" field.
+func (u *PaymentOrderUpsertOne) SetPayURL(v string) *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetPayURL(v)
+ })
+}
+
+// UpdatePayURL sets the "pay_url" field to the value that was provided on create.
+func (u *PaymentOrderUpsertOne) UpdatePayURL() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdatePayURL()
+ })
+}
+
+// ClearPayURL clears the value of the "pay_url" field.
+func (u *PaymentOrderUpsertOne) ClearPayURL() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.ClearPayURL()
+ })
+}
+
+// SetQrCode sets the "qr_code" field.
+func (u *PaymentOrderUpsertOne) SetQrCode(v string) *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetQrCode(v)
+ })
+}
+
+// UpdateQrCode sets the "qr_code" field to the value that was provided on create.
+func (u *PaymentOrderUpsertOne) UpdateQrCode() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateQrCode()
+ })
+}
+
+// ClearQrCode clears the value of the "qr_code" field.
+func (u *PaymentOrderUpsertOne) ClearQrCode() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.ClearQrCode()
+ })
+}
+
+// SetQrCodeImg sets the "qr_code_img" field.
+func (u *PaymentOrderUpsertOne) SetQrCodeImg(v string) *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetQrCodeImg(v)
+ })
+}
+
+// UpdateQrCodeImg sets the "qr_code_img" field to the value that was provided on create.
+func (u *PaymentOrderUpsertOne) UpdateQrCodeImg() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateQrCodeImg()
+ })
+}
+
+// ClearQrCodeImg clears the value of the "qr_code_img" field.
+func (u *PaymentOrderUpsertOne) ClearQrCodeImg() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.ClearQrCodeImg()
+ })
+}
+
+// SetOrderType sets the "order_type" field.
+func (u *PaymentOrderUpsertOne) SetOrderType(v string) *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetOrderType(v)
+ })
+}
+
+// UpdateOrderType sets the "order_type" field to the value that was provided on create.
+func (u *PaymentOrderUpsertOne) UpdateOrderType() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateOrderType()
+ })
+}
+
+// SetPlanID sets the "plan_id" field.
+func (u *PaymentOrderUpsertOne) SetPlanID(v int64) *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetPlanID(v)
+ })
+}
+
+// AddPlanID adds v to the "plan_id" field.
+func (u *PaymentOrderUpsertOne) AddPlanID(v int64) *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.AddPlanID(v)
+ })
+}
+
+// UpdatePlanID sets the "plan_id" field to the value that was provided on create.
+func (u *PaymentOrderUpsertOne) UpdatePlanID() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdatePlanID()
+ })
+}
+
+// ClearPlanID clears the value of the "plan_id" field.
+func (u *PaymentOrderUpsertOne) ClearPlanID() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.ClearPlanID()
+ })
+}
+
+// SetSubscriptionGroupID sets the "subscription_group_id" field.
+func (u *PaymentOrderUpsertOne) SetSubscriptionGroupID(v int64) *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetSubscriptionGroupID(v)
+ })
+}
+
+// AddSubscriptionGroupID adds v to the "subscription_group_id" field.
+func (u *PaymentOrderUpsertOne) AddSubscriptionGroupID(v int64) *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.AddSubscriptionGroupID(v)
+ })
+}
+
+// UpdateSubscriptionGroupID sets the "subscription_group_id" field to the value that was provided on create.
+func (u *PaymentOrderUpsertOne) UpdateSubscriptionGroupID() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateSubscriptionGroupID()
+ })
+}
+
+// ClearSubscriptionGroupID clears the value of the "subscription_group_id" field.
+func (u *PaymentOrderUpsertOne) ClearSubscriptionGroupID() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.ClearSubscriptionGroupID()
+ })
+}
+
+// SetSubscriptionDays sets the "subscription_days" field.
+func (u *PaymentOrderUpsertOne) SetSubscriptionDays(v int) *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetSubscriptionDays(v)
+ })
+}
+
+// AddSubscriptionDays adds v to the "subscription_days" field.
+func (u *PaymentOrderUpsertOne) AddSubscriptionDays(v int) *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.AddSubscriptionDays(v)
+ })
+}
+
+// UpdateSubscriptionDays sets the "subscription_days" field to the value that was provided on create.
+func (u *PaymentOrderUpsertOne) UpdateSubscriptionDays() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateSubscriptionDays()
+ })
+}
+
+// ClearSubscriptionDays clears the value of the "subscription_days" field.
+func (u *PaymentOrderUpsertOne) ClearSubscriptionDays() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.ClearSubscriptionDays()
+ })
+}
+
+// SetProviderInstanceID sets the "provider_instance_id" field.
+func (u *PaymentOrderUpsertOne) SetProviderInstanceID(v string) *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetProviderInstanceID(v)
+ })
+}
+
+// UpdateProviderInstanceID sets the "provider_instance_id" field to the value that was provided on create.
+func (u *PaymentOrderUpsertOne) UpdateProviderInstanceID() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateProviderInstanceID()
+ })
+}
+
+// ClearProviderInstanceID clears the value of the "provider_instance_id" field.
+func (u *PaymentOrderUpsertOne) ClearProviderInstanceID() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.ClearProviderInstanceID()
+ })
+}
+
+// SetStatus sets the "status" field.
+func (u *PaymentOrderUpsertOne) SetStatus(v string) *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetStatus(v)
+ })
+}
+
+// UpdateStatus sets the "status" field to the value that was provided on create.
+func (u *PaymentOrderUpsertOne) UpdateStatus() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateStatus()
+ })
+}
+
+// SetRefundAmount sets the "refund_amount" field.
+func (u *PaymentOrderUpsertOne) SetRefundAmount(v float64) *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetRefundAmount(v)
+ })
+}
+
+// AddRefundAmount adds v to the "refund_amount" field.
+func (u *PaymentOrderUpsertOne) AddRefundAmount(v float64) *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.AddRefundAmount(v)
+ })
+}
+
+// UpdateRefundAmount sets the "refund_amount" field to the value that was provided on create.
+func (u *PaymentOrderUpsertOne) UpdateRefundAmount() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateRefundAmount()
+ })
+}
+
+// SetRefundReason sets the "refund_reason" field.
+func (u *PaymentOrderUpsertOne) SetRefundReason(v string) *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetRefundReason(v)
+ })
+}
+
+// UpdateRefundReason sets the "refund_reason" field to the value that was provided on create.
+func (u *PaymentOrderUpsertOne) UpdateRefundReason() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateRefundReason()
+ })
+}
+
+// ClearRefundReason clears the value of the "refund_reason" field.
+func (u *PaymentOrderUpsertOne) ClearRefundReason() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.ClearRefundReason()
+ })
+}
+
+// SetRefundAt sets the "refund_at" field.
+func (u *PaymentOrderUpsertOne) SetRefundAt(v time.Time) *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetRefundAt(v)
+ })
+}
+
+// UpdateRefundAt sets the "refund_at" field to the value that was provided on create.
+func (u *PaymentOrderUpsertOne) UpdateRefundAt() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateRefundAt()
+ })
+}
+
+// ClearRefundAt clears the value of the "refund_at" field.
+func (u *PaymentOrderUpsertOne) ClearRefundAt() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.ClearRefundAt()
+ })
+}
+
+// SetForceRefund sets the "force_refund" field.
+func (u *PaymentOrderUpsertOne) SetForceRefund(v bool) *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetForceRefund(v)
+ })
+}
+
+// UpdateForceRefund sets the "force_refund" field to the value that was provided on create.
+func (u *PaymentOrderUpsertOne) UpdateForceRefund() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateForceRefund()
+ })
+}
+
+// SetRefundRequestedAt sets the "refund_requested_at" field.
+func (u *PaymentOrderUpsertOne) SetRefundRequestedAt(v time.Time) *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetRefundRequestedAt(v)
+ })
+}
+
+// UpdateRefundRequestedAt sets the "refund_requested_at" field to the value that was provided on create.
+func (u *PaymentOrderUpsertOne) UpdateRefundRequestedAt() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateRefundRequestedAt()
+ })
+}
+
+// ClearRefundRequestedAt clears the value of the "refund_requested_at" field.
+func (u *PaymentOrderUpsertOne) ClearRefundRequestedAt() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.ClearRefundRequestedAt()
+ })
+}
+
+// SetRefundRequestReason sets the "refund_request_reason" field.
+func (u *PaymentOrderUpsertOne) SetRefundRequestReason(v string) *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetRefundRequestReason(v)
+ })
+}
+
+// UpdateRefundRequestReason sets the "refund_request_reason" field to the value that was provided on create.
+func (u *PaymentOrderUpsertOne) UpdateRefundRequestReason() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateRefundRequestReason()
+ })
+}
+
+// ClearRefundRequestReason clears the value of the "refund_request_reason" field.
+func (u *PaymentOrderUpsertOne) ClearRefundRequestReason() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.ClearRefundRequestReason()
+ })
+}
+
+// SetRefundRequestedBy sets the "refund_requested_by" field.
+func (u *PaymentOrderUpsertOne) SetRefundRequestedBy(v string) *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetRefundRequestedBy(v)
+ })
+}
+
+// UpdateRefundRequestedBy sets the "refund_requested_by" field to the value that was provided on create.
+func (u *PaymentOrderUpsertOne) UpdateRefundRequestedBy() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateRefundRequestedBy()
+ })
+}
+
+// ClearRefundRequestedBy clears the value of the "refund_requested_by" field.
+func (u *PaymentOrderUpsertOne) ClearRefundRequestedBy() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.ClearRefundRequestedBy()
+ })
+}
+
+// SetExpiresAt sets the "expires_at" field.
+func (u *PaymentOrderUpsertOne) SetExpiresAt(v time.Time) *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetExpiresAt(v)
+ })
+}
+
+// UpdateExpiresAt sets the "expires_at" field to the value that was provided on create.
+func (u *PaymentOrderUpsertOne) UpdateExpiresAt() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateExpiresAt()
+ })
+}
+
+// SetPaidAt sets the "paid_at" field.
+func (u *PaymentOrderUpsertOne) SetPaidAt(v time.Time) *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetPaidAt(v)
+ })
+}
+
+// UpdatePaidAt sets the "paid_at" field to the value that was provided on create.
+func (u *PaymentOrderUpsertOne) UpdatePaidAt() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdatePaidAt()
+ })
+}
+
+// ClearPaidAt clears the value of the "paid_at" field.
+func (u *PaymentOrderUpsertOne) ClearPaidAt() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.ClearPaidAt()
+ })
+}
+
+// SetCompletedAt sets the "completed_at" field.
+func (u *PaymentOrderUpsertOne) SetCompletedAt(v time.Time) *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetCompletedAt(v)
+ })
+}
+
+// UpdateCompletedAt sets the "completed_at" field to the value that was provided on create.
+func (u *PaymentOrderUpsertOne) UpdateCompletedAt() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateCompletedAt()
+ })
+}
+
+// ClearCompletedAt clears the value of the "completed_at" field.
+func (u *PaymentOrderUpsertOne) ClearCompletedAt() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.ClearCompletedAt()
+ })
+}
+
+// SetFailedAt sets the "failed_at" field.
+func (u *PaymentOrderUpsertOne) SetFailedAt(v time.Time) *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetFailedAt(v)
+ })
+}
+
+// UpdateFailedAt sets the "failed_at" field to the value that was provided on create.
+func (u *PaymentOrderUpsertOne) UpdateFailedAt() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateFailedAt()
+ })
+}
+
+// ClearFailedAt clears the value of the "failed_at" field.
+func (u *PaymentOrderUpsertOne) ClearFailedAt() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.ClearFailedAt()
+ })
+}
+
+// SetFailedReason sets the "failed_reason" field.
+func (u *PaymentOrderUpsertOne) SetFailedReason(v string) *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetFailedReason(v)
+ })
+}
+
+// UpdateFailedReason sets the "failed_reason" field to the value that was provided on create.
+func (u *PaymentOrderUpsertOne) UpdateFailedReason() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateFailedReason()
+ })
+}
+
+// ClearFailedReason clears the value of the "failed_reason" field.
+func (u *PaymentOrderUpsertOne) ClearFailedReason() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.ClearFailedReason()
+ })
+}
+
+// SetClientIP sets the "client_ip" field.
+func (u *PaymentOrderUpsertOne) SetClientIP(v string) *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetClientIP(v)
+ })
+}
+
+// UpdateClientIP sets the "client_ip" field to the value that was provided on create.
+func (u *PaymentOrderUpsertOne) UpdateClientIP() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateClientIP()
+ })
+}
+
+// SetSrcHost sets the "src_host" field.
+func (u *PaymentOrderUpsertOne) SetSrcHost(v string) *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetSrcHost(v)
+ })
+}
+
+// UpdateSrcHost sets the "src_host" field to the value that was provided on create.
+func (u *PaymentOrderUpsertOne) UpdateSrcHost() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateSrcHost()
+ })
+}
+
+// SetSrcURL sets the "src_url" field.
+func (u *PaymentOrderUpsertOne) SetSrcURL(v string) *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetSrcURL(v)
+ })
+}
+
+// UpdateSrcURL sets the "src_url" field to the value that was provided on create.
+func (u *PaymentOrderUpsertOne) UpdateSrcURL() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateSrcURL()
+ })
+}
+
+// ClearSrcURL clears the value of the "src_url" field.
+func (u *PaymentOrderUpsertOne) ClearSrcURL() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.ClearSrcURL()
+ })
+}
+
+// SetUpdatedAt sets the "updated_at" field.
+func (u *PaymentOrderUpsertOne) SetUpdatedAt(v time.Time) *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetUpdatedAt(v)
+ })
+}
+
+// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create.
+func (u *PaymentOrderUpsertOne) UpdateUpdatedAt() *PaymentOrderUpsertOne {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateUpdatedAt()
+ })
+}
+
+// Exec executes the query.
+func (u *PaymentOrderUpsertOne) Exec(ctx context.Context) error {
+ if len(u.create.conflict) == 0 {
+ return errors.New("ent: missing options for PaymentOrderCreate.OnConflict")
+ }
+ return u.create.Exec(ctx)
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (u *PaymentOrderUpsertOne) 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 *PaymentOrderUpsertOne) 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 *PaymentOrderUpsertOne) IDX(ctx context.Context) int64 {
+ id, err := u.ID(ctx)
+ if err != nil {
+ panic(err)
+ }
+ return id
+}
+
+// PaymentOrderCreateBulk is the builder for creating many PaymentOrder entities in bulk.
+type PaymentOrderCreateBulk struct {
+ config
+ err error
+ builders []*PaymentOrderCreate
+ conflict []sql.ConflictOption
+}
+
+// Save creates the PaymentOrder entities in the database.
+func (_c *PaymentOrderCreateBulk) Save(ctx context.Context) ([]*PaymentOrder, error) {
+ if _c.err != nil {
+ return nil, _c.err
+ }
+ specs := make([]*sqlgraph.CreateSpec, len(_c.builders))
+ nodes := make([]*PaymentOrder, 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.(*PaymentOrderMutation)
+ 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 *PaymentOrderCreateBulk) SaveX(ctx context.Context) []*PaymentOrder {
+ v, err := _c.Save(ctx)
+ if err != nil {
+ panic(err)
+ }
+ return v
+}
+
+// Exec executes the query.
+func (_c *PaymentOrderCreateBulk) Exec(ctx context.Context) error {
+ _, err := _c.Save(ctx)
+ return err
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (_c *PaymentOrderCreateBulk) 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.PaymentOrder.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.PaymentOrderUpsert) {
+// SetUserID(v+v).
+// }).
+// Exec(ctx)
+func (_c *PaymentOrderCreateBulk) OnConflict(opts ...sql.ConflictOption) *PaymentOrderUpsertBulk {
+ _c.conflict = opts
+ return &PaymentOrderUpsertBulk{
+ create: _c,
+ }
+}
+
+// OnConflictColumns calls `OnConflict` and configures the columns
+// as conflict target. Using this option is equivalent to using:
+//
+// client.PaymentOrder.Create().
+// OnConflict(sql.ConflictColumns(columns...)).
+// Exec(ctx)
+func (_c *PaymentOrderCreateBulk) OnConflictColumns(columns ...string) *PaymentOrderUpsertBulk {
+ _c.conflict = append(_c.conflict, sql.ConflictColumns(columns...))
+ return &PaymentOrderUpsertBulk{
+ create: _c,
+ }
+}
+
+// PaymentOrderUpsertBulk is the builder for "upsert"-ing
+// a bulk of PaymentOrder nodes.
+type PaymentOrderUpsertBulk struct {
+ create *PaymentOrderCreateBulk
+}
+
+// UpdateNewValues updates the mutable fields using the new values that
+// were set on create. Using this option is equivalent to using:
+//
+// client.PaymentOrder.Create().
+// OnConflict(
+// sql.ResolveWithNewValues(),
+// ).
+// Exec(ctx)
+func (u *PaymentOrderUpsertBulk) UpdateNewValues() *PaymentOrderUpsertBulk {
+ 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(paymentorder.FieldCreatedAt)
+ }
+ }
+ }))
+ return u
+}
+
+// Ignore sets each column to itself in case of conflict.
+// Using this option is equivalent to using:
+//
+// client.PaymentOrder.Create().
+// OnConflict(sql.ResolveWithIgnore()).
+// Exec(ctx)
+func (u *PaymentOrderUpsertBulk) Ignore() *PaymentOrderUpsertBulk {
+ 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 *PaymentOrderUpsertBulk) DoNothing() *PaymentOrderUpsertBulk {
+ u.create.conflict = append(u.create.conflict, sql.DoNothing())
+ return u
+}
+
+// Update allows overriding fields `UPDATE` values. See the PaymentOrderCreateBulk.OnConflict
+// documentation for more info.
+func (u *PaymentOrderUpsertBulk) Update(set func(*PaymentOrderUpsert)) *PaymentOrderUpsertBulk {
+ u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) {
+ set(&PaymentOrderUpsert{UpdateSet: update})
+ }))
+ return u
+}
+
+// SetUserID sets the "user_id" field.
+func (u *PaymentOrderUpsertBulk) SetUserID(v int64) *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetUserID(v)
+ })
+}
+
+// UpdateUserID sets the "user_id" field to the value that was provided on create.
+func (u *PaymentOrderUpsertBulk) UpdateUserID() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateUserID()
+ })
+}
+
+// SetUserEmail sets the "user_email" field.
+func (u *PaymentOrderUpsertBulk) SetUserEmail(v string) *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetUserEmail(v)
+ })
+}
+
+// UpdateUserEmail sets the "user_email" field to the value that was provided on create.
+func (u *PaymentOrderUpsertBulk) UpdateUserEmail() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateUserEmail()
+ })
+}
+
+// SetUserName sets the "user_name" field.
+func (u *PaymentOrderUpsertBulk) SetUserName(v string) *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetUserName(v)
+ })
+}
+
+// UpdateUserName sets the "user_name" field to the value that was provided on create.
+func (u *PaymentOrderUpsertBulk) UpdateUserName() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateUserName()
+ })
+}
+
+// SetUserNotes sets the "user_notes" field.
+func (u *PaymentOrderUpsertBulk) SetUserNotes(v string) *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetUserNotes(v)
+ })
+}
+
+// UpdateUserNotes sets the "user_notes" field to the value that was provided on create.
+func (u *PaymentOrderUpsertBulk) UpdateUserNotes() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateUserNotes()
+ })
+}
+
+// ClearUserNotes clears the value of the "user_notes" field.
+func (u *PaymentOrderUpsertBulk) ClearUserNotes() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.ClearUserNotes()
+ })
+}
+
+// SetAmount sets the "amount" field.
+func (u *PaymentOrderUpsertBulk) SetAmount(v float64) *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetAmount(v)
+ })
+}
+
+// AddAmount adds v to the "amount" field.
+func (u *PaymentOrderUpsertBulk) AddAmount(v float64) *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.AddAmount(v)
+ })
+}
+
+// UpdateAmount sets the "amount" field to the value that was provided on create.
+func (u *PaymentOrderUpsertBulk) UpdateAmount() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateAmount()
+ })
+}
+
+// SetPayAmount sets the "pay_amount" field.
+func (u *PaymentOrderUpsertBulk) SetPayAmount(v float64) *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetPayAmount(v)
+ })
+}
+
+// AddPayAmount adds v to the "pay_amount" field.
+func (u *PaymentOrderUpsertBulk) AddPayAmount(v float64) *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.AddPayAmount(v)
+ })
+}
+
+// UpdatePayAmount sets the "pay_amount" field to the value that was provided on create.
+func (u *PaymentOrderUpsertBulk) UpdatePayAmount() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdatePayAmount()
+ })
+}
+
+// SetFeeRate sets the "fee_rate" field.
+func (u *PaymentOrderUpsertBulk) SetFeeRate(v float64) *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetFeeRate(v)
+ })
+}
+
+// AddFeeRate adds v to the "fee_rate" field.
+func (u *PaymentOrderUpsertBulk) AddFeeRate(v float64) *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.AddFeeRate(v)
+ })
+}
+
+// UpdateFeeRate sets the "fee_rate" field to the value that was provided on create.
+func (u *PaymentOrderUpsertBulk) UpdateFeeRate() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateFeeRate()
+ })
+}
+
+// SetRechargeCode sets the "recharge_code" field.
+func (u *PaymentOrderUpsertBulk) SetRechargeCode(v string) *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetRechargeCode(v)
+ })
+}
+
+// UpdateRechargeCode sets the "recharge_code" field to the value that was provided on create.
+func (u *PaymentOrderUpsertBulk) UpdateRechargeCode() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateRechargeCode()
+ })
+}
+
+// SetOutTradeNo sets the "out_trade_no" field.
+func (u *PaymentOrderUpsertBulk) SetOutTradeNo(v string) *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetOutTradeNo(v)
+ })
+}
+
+// UpdateOutTradeNo sets the "out_trade_no" field to the value that was provided on create.
+func (u *PaymentOrderUpsertBulk) UpdateOutTradeNo() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateOutTradeNo()
+ })
+}
+
+// SetPaymentType sets the "payment_type" field.
+func (u *PaymentOrderUpsertBulk) SetPaymentType(v string) *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetPaymentType(v)
+ })
+}
+
+// UpdatePaymentType sets the "payment_type" field to the value that was provided on create.
+func (u *PaymentOrderUpsertBulk) UpdatePaymentType() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdatePaymentType()
+ })
+}
+
+// SetPaymentTradeNo sets the "payment_trade_no" field.
+func (u *PaymentOrderUpsertBulk) SetPaymentTradeNo(v string) *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetPaymentTradeNo(v)
+ })
+}
+
+// UpdatePaymentTradeNo sets the "payment_trade_no" field to the value that was provided on create.
+func (u *PaymentOrderUpsertBulk) UpdatePaymentTradeNo() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdatePaymentTradeNo()
+ })
+}
+
+// SetPayURL sets the "pay_url" field.
+func (u *PaymentOrderUpsertBulk) SetPayURL(v string) *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetPayURL(v)
+ })
+}
+
+// UpdatePayURL sets the "pay_url" field to the value that was provided on create.
+func (u *PaymentOrderUpsertBulk) UpdatePayURL() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdatePayURL()
+ })
+}
+
+// ClearPayURL clears the value of the "pay_url" field.
+func (u *PaymentOrderUpsertBulk) ClearPayURL() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.ClearPayURL()
+ })
+}
+
+// SetQrCode sets the "qr_code" field.
+func (u *PaymentOrderUpsertBulk) SetQrCode(v string) *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetQrCode(v)
+ })
+}
+
+// UpdateQrCode sets the "qr_code" field to the value that was provided on create.
+func (u *PaymentOrderUpsertBulk) UpdateQrCode() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateQrCode()
+ })
+}
+
+// ClearQrCode clears the value of the "qr_code" field.
+func (u *PaymentOrderUpsertBulk) ClearQrCode() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.ClearQrCode()
+ })
+}
+
+// SetQrCodeImg sets the "qr_code_img" field.
+func (u *PaymentOrderUpsertBulk) SetQrCodeImg(v string) *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetQrCodeImg(v)
+ })
+}
+
+// UpdateQrCodeImg sets the "qr_code_img" field to the value that was provided on create.
+func (u *PaymentOrderUpsertBulk) UpdateQrCodeImg() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateQrCodeImg()
+ })
+}
+
+// ClearQrCodeImg clears the value of the "qr_code_img" field.
+func (u *PaymentOrderUpsertBulk) ClearQrCodeImg() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.ClearQrCodeImg()
+ })
+}
+
+// SetOrderType sets the "order_type" field.
+func (u *PaymentOrderUpsertBulk) SetOrderType(v string) *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetOrderType(v)
+ })
+}
+
+// UpdateOrderType sets the "order_type" field to the value that was provided on create.
+func (u *PaymentOrderUpsertBulk) UpdateOrderType() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateOrderType()
+ })
+}
+
+// SetPlanID sets the "plan_id" field.
+func (u *PaymentOrderUpsertBulk) SetPlanID(v int64) *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetPlanID(v)
+ })
+}
+
+// AddPlanID adds v to the "plan_id" field.
+func (u *PaymentOrderUpsertBulk) AddPlanID(v int64) *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.AddPlanID(v)
+ })
+}
+
+// UpdatePlanID sets the "plan_id" field to the value that was provided on create.
+func (u *PaymentOrderUpsertBulk) UpdatePlanID() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdatePlanID()
+ })
+}
+
+// ClearPlanID clears the value of the "plan_id" field.
+func (u *PaymentOrderUpsertBulk) ClearPlanID() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.ClearPlanID()
+ })
+}
+
+// SetSubscriptionGroupID sets the "subscription_group_id" field.
+func (u *PaymentOrderUpsertBulk) SetSubscriptionGroupID(v int64) *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetSubscriptionGroupID(v)
+ })
+}
+
+// AddSubscriptionGroupID adds v to the "subscription_group_id" field.
+func (u *PaymentOrderUpsertBulk) AddSubscriptionGroupID(v int64) *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.AddSubscriptionGroupID(v)
+ })
+}
+
+// UpdateSubscriptionGroupID sets the "subscription_group_id" field to the value that was provided on create.
+func (u *PaymentOrderUpsertBulk) UpdateSubscriptionGroupID() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateSubscriptionGroupID()
+ })
+}
+
+// ClearSubscriptionGroupID clears the value of the "subscription_group_id" field.
+func (u *PaymentOrderUpsertBulk) ClearSubscriptionGroupID() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.ClearSubscriptionGroupID()
+ })
+}
+
+// SetSubscriptionDays sets the "subscription_days" field.
+func (u *PaymentOrderUpsertBulk) SetSubscriptionDays(v int) *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetSubscriptionDays(v)
+ })
+}
+
+// AddSubscriptionDays adds v to the "subscription_days" field.
+func (u *PaymentOrderUpsertBulk) AddSubscriptionDays(v int) *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.AddSubscriptionDays(v)
+ })
+}
+
+// UpdateSubscriptionDays sets the "subscription_days" field to the value that was provided on create.
+func (u *PaymentOrderUpsertBulk) UpdateSubscriptionDays() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateSubscriptionDays()
+ })
+}
+
+// ClearSubscriptionDays clears the value of the "subscription_days" field.
+func (u *PaymentOrderUpsertBulk) ClearSubscriptionDays() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.ClearSubscriptionDays()
+ })
+}
+
+// SetProviderInstanceID sets the "provider_instance_id" field.
+func (u *PaymentOrderUpsertBulk) SetProviderInstanceID(v string) *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetProviderInstanceID(v)
+ })
+}
+
+// UpdateProviderInstanceID sets the "provider_instance_id" field to the value that was provided on create.
+func (u *PaymentOrderUpsertBulk) UpdateProviderInstanceID() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateProviderInstanceID()
+ })
+}
+
+// ClearProviderInstanceID clears the value of the "provider_instance_id" field.
+func (u *PaymentOrderUpsertBulk) ClearProviderInstanceID() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.ClearProviderInstanceID()
+ })
+}
+
+// SetStatus sets the "status" field.
+func (u *PaymentOrderUpsertBulk) SetStatus(v string) *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetStatus(v)
+ })
+}
+
+// UpdateStatus sets the "status" field to the value that was provided on create.
+func (u *PaymentOrderUpsertBulk) UpdateStatus() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateStatus()
+ })
+}
+
+// SetRefundAmount sets the "refund_amount" field.
+func (u *PaymentOrderUpsertBulk) SetRefundAmount(v float64) *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetRefundAmount(v)
+ })
+}
+
+// AddRefundAmount adds v to the "refund_amount" field.
+func (u *PaymentOrderUpsertBulk) AddRefundAmount(v float64) *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.AddRefundAmount(v)
+ })
+}
+
+// UpdateRefundAmount sets the "refund_amount" field to the value that was provided on create.
+func (u *PaymentOrderUpsertBulk) UpdateRefundAmount() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateRefundAmount()
+ })
+}
+
+// SetRefundReason sets the "refund_reason" field.
+func (u *PaymentOrderUpsertBulk) SetRefundReason(v string) *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetRefundReason(v)
+ })
+}
+
+// UpdateRefundReason sets the "refund_reason" field to the value that was provided on create.
+func (u *PaymentOrderUpsertBulk) UpdateRefundReason() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateRefundReason()
+ })
+}
+
+// ClearRefundReason clears the value of the "refund_reason" field.
+func (u *PaymentOrderUpsertBulk) ClearRefundReason() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.ClearRefundReason()
+ })
+}
+
+// SetRefundAt sets the "refund_at" field.
+func (u *PaymentOrderUpsertBulk) SetRefundAt(v time.Time) *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetRefundAt(v)
+ })
+}
+
+// UpdateRefundAt sets the "refund_at" field to the value that was provided on create.
+func (u *PaymentOrderUpsertBulk) UpdateRefundAt() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateRefundAt()
+ })
+}
+
+// ClearRefundAt clears the value of the "refund_at" field.
+func (u *PaymentOrderUpsertBulk) ClearRefundAt() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.ClearRefundAt()
+ })
+}
+
+// SetForceRefund sets the "force_refund" field.
+func (u *PaymentOrderUpsertBulk) SetForceRefund(v bool) *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetForceRefund(v)
+ })
+}
+
+// UpdateForceRefund sets the "force_refund" field to the value that was provided on create.
+func (u *PaymentOrderUpsertBulk) UpdateForceRefund() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateForceRefund()
+ })
+}
+
+// SetRefundRequestedAt sets the "refund_requested_at" field.
+func (u *PaymentOrderUpsertBulk) SetRefundRequestedAt(v time.Time) *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetRefundRequestedAt(v)
+ })
+}
+
+// UpdateRefundRequestedAt sets the "refund_requested_at" field to the value that was provided on create.
+func (u *PaymentOrderUpsertBulk) UpdateRefundRequestedAt() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateRefundRequestedAt()
+ })
+}
+
+// ClearRefundRequestedAt clears the value of the "refund_requested_at" field.
+func (u *PaymentOrderUpsertBulk) ClearRefundRequestedAt() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.ClearRefundRequestedAt()
+ })
+}
+
+// SetRefundRequestReason sets the "refund_request_reason" field.
+func (u *PaymentOrderUpsertBulk) SetRefundRequestReason(v string) *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetRefundRequestReason(v)
+ })
+}
+
+// UpdateRefundRequestReason sets the "refund_request_reason" field to the value that was provided on create.
+func (u *PaymentOrderUpsertBulk) UpdateRefundRequestReason() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateRefundRequestReason()
+ })
+}
+
+// ClearRefundRequestReason clears the value of the "refund_request_reason" field.
+func (u *PaymentOrderUpsertBulk) ClearRefundRequestReason() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.ClearRefundRequestReason()
+ })
+}
+
+// SetRefundRequestedBy sets the "refund_requested_by" field.
+func (u *PaymentOrderUpsertBulk) SetRefundRequestedBy(v string) *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetRefundRequestedBy(v)
+ })
+}
+
+// UpdateRefundRequestedBy sets the "refund_requested_by" field to the value that was provided on create.
+func (u *PaymentOrderUpsertBulk) UpdateRefundRequestedBy() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateRefundRequestedBy()
+ })
+}
+
+// ClearRefundRequestedBy clears the value of the "refund_requested_by" field.
+func (u *PaymentOrderUpsertBulk) ClearRefundRequestedBy() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.ClearRefundRequestedBy()
+ })
+}
+
+// SetExpiresAt sets the "expires_at" field.
+func (u *PaymentOrderUpsertBulk) SetExpiresAt(v time.Time) *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetExpiresAt(v)
+ })
+}
+
+// UpdateExpiresAt sets the "expires_at" field to the value that was provided on create.
+func (u *PaymentOrderUpsertBulk) UpdateExpiresAt() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateExpiresAt()
+ })
+}
+
+// SetPaidAt sets the "paid_at" field.
+func (u *PaymentOrderUpsertBulk) SetPaidAt(v time.Time) *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetPaidAt(v)
+ })
+}
+
+// UpdatePaidAt sets the "paid_at" field to the value that was provided on create.
+func (u *PaymentOrderUpsertBulk) UpdatePaidAt() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdatePaidAt()
+ })
+}
+
+// ClearPaidAt clears the value of the "paid_at" field.
+func (u *PaymentOrderUpsertBulk) ClearPaidAt() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.ClearPaidAt()
+ })
+}
+
+// SetCompletedAt sets the "completed_at" field.
+func (u *PaymentOrderUpsertBulk) SetCompletedAt(v time.Time) *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetCompletedAt(v)
+ })
+}
+
+// UpdateCompletedAt sets the "completed_at" field to the value that was provided on create.
+func (u *PaymentOrderUpsertBulk) UpdateCompletedAt() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateCompletedAt()
+ })
+}
+
+// ClearCompletedAt clears the value of the "completed_at" field.
+func (u *PaymentOrderUpsertBulk) ClearCompletedAt() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.ClearCompletedAt()
+ })
+}
+
+// SetFailedAt sets the "failed_at" field.
+func (u *PaymentOrderUpsertBulk) SetFailedAt(v time.Time) *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetFailedAt(v)
+ })
+}
+
+// UpdateFailedAt sets the "failed_at" field to the value that was provided on create.
+func (u *PaymentOrderUpsertBulk) UpdateFailedAt() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateFailedAt()
+ })
+}
+
+// ClearFailedAt clears the value of the "failed_at" field.
+func (u *PaymentOrderUpsertBulk) ClearFailedAt() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.ClearFailedAt()
+ })
+}
+
+// SetFailedReason sets the "failed_reason" field.
+func (u *PaymentOrderUpsertBulk) SetFailedReason(v string) *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetFailedReason(v)
+ })
+}
+
+// UpdateFailedReason sets the "failed_reason" field to the value that was provided on create.
+func (u *PaymentOrderUpsertBulk) UpdateFailedReason() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateFailedReason()
+ })
+}
+
+// ClearFailedReason clears the value of the "failed_reason" field.
+func (u *PaymentOrderUpsertBulk) ClearFailedReason() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.ClearFailedReason()
+ })
+}
+
+// SetClientIP sets the "client_ip" field.
+func (u *PaymentOrderUpsertBulk) SetClientIP(v string) *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetClientIP(v)
+ })
+}
+
+// UpdateClientIP sets the "client_ip" field to the value that was provided on create.
+func (u *PaymentOrderUpsertBulk) UpdateClientIP() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateClientIP()
+ })
+}
+
+// SetSrcHost sets the "src_host" field.
+func (u *PaymentOrderUpsertBulk) SetSrcHost(v string) *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetSrcHost(v)
+ })
+}
+
+// UpdateSrcHost sets the "src_host" field to the value that was provided on create.
+func (u *PaymentOrderUpsertBulk) UpdateSrcHost() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateSrcHost()
+ })
+}
+
+// SetSrcURL sets the "src_url" field.
+func (u *PaymentOrderUpsertBulk) SetSrcURL(v string) *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetSrcURL(v)
+ })
+}
+
+// UpdateSrcURL sets the "src_url" field to the value that was provided on create.
+func (u *PaymentOrderUpsertBulk) UpdateSrcURL() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateSrcURL()
+ })
+}
+
+// ClearSrcURL clears the value of the "src_url" field.
+func (u *PaymentOrderUpsertBulk) ClearSrcURL() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.ClearSrcURL()
+ })
+}
+
+// SetUpdatedAt sets the "updated_at" field.
+func (u *PaymentOrderUpsertBulk) SetUpdatedAt(v time.Time) *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.SetUpdatedAt(v)
+ })
+}
+
+// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create.
+func (u *PaymentOrderUpsertBulk) UpdateUpdatedAt() *PaymentOrderUpsertBulk {
+ return u.Update(func(s *PaymentOrderUpsert) {
+ s.UpdateUpdatedAt()
+ })
+}
+
+// Exec executes the query.
+func (u *PaymentOrderUpsertBulk) 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 PaymentOrderCreateBulk instead", i)
+ }
+ }
+ if len(u.create.conflict) == 0 {
+ return errors.New("ent: missing options for PaymentOrderCreateBulk.OnConflict")
+ }
+ return u.create.Exec(ctx)
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (u *PaymentOrderUpsertBulk) ExecX(ctx context.Context) {
+ if err := u.create.Exec(ctx); err != nil {
+ panic(err)
+ }
+}
diff --git a/backend/ent/paymentorder_delete.go b/backend/ent/paymentorder_delete.go
new file mode 100644
index 0000000000000000000000000000000000000000..a4bc1bdf6d124a595c38d295acf28930de3933b5
--- /dev/null
+++ b/backend/ent/paymentorder_delete.go
@@ -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)
+ }
+}
diff --git a/backend/ent/paymentorder_query.go b/backend/ent/paymentorder_query.go
new file mode 100644
index 0000000000000000000000000000000000000000..92fd74a73c48a97178c1f98e2b49b6566976b39d
--- /dev/null
+++ b/backend/ent/paymentorder_query.go
@@ -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)
+}
diff --git a/backend/ent/paymentorder_update.go b/backend/ent/paymentorder_update.go
new file mode 100644
index 0000000000000000000000000000000000000000..5978fc29148618f828e6586f8baf59d24c64ed1f
--- /dev/null
+++ b/backend/ent/paymentorder_update.go
@@ -0,0 +1,2083 @@
+// 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/paymentorder"
+ "github.com/Wei-Shaw/sub2api/ent/predicate"
+ "github.com/Wei-Shaw/sub2api/ent/user"
+)
+
+// PaymentOrderUpdate is the builder for updating PaymentOrder entities.
+type PaymentOrderUpdate struct {
+ config
+ hooks []Hook
+ mutation *PaymentOrderMutation
+}
+
+// Where appends a list predicates to the PaymentOrderUpdate builder.
+func (_u *PaymentOrderUpdate) Where(ps ...predicate.PaymentOrder) *PaymentOrderUpdate {
+ _u.mutation.Where(ps...)
+ return _u
+}
+
+// SetUserID sets the "user_id" field.
+func (_u *PaymentOrderUpdate) SetUserID(v int64) *PaymentOrderUpdate {
+ _u.mutation.SetUserID(v)
+ return _u
+}
+
+// SetNillableUserID sets the "user_id" field if the given value is not nil.
+func (_u *PaymentOrderUpdate) SetNillableUserID(v *int64) *PaymentOrderUpdate {
+ if v != nil {
+ _u.SetUserID(*v)
+ }
+ return _u
+}
+
+// SetUserEmail sets the "user_email" field.
+func (_u *PaymentOrderUpdate) SetUserEmail(v string) *PaymentOrderUpdate {
+ _u.mutation.SetUserEmail(v)
+ return _u
+}
+
+// SetNillableUserEmail sets the "user_email" field if the given value is not nil.
+func (_u *PaymentOrderUpdate) SetNillableUserEmail(v *string) *PaymentOrderUpdate {
+ if v != nil {
+ _u.SetUserEmail(*v)
+ }
+ return _u
+}
+
+// SetUserName sets the "user_name" field.
+func (_u *PaymentOrderUpdate) SetUserName(v string) *PaymentOrderUpdate {
+ _u.mutation.SetUserName(v)
+ return _u
+}
+
+// SetNillableUserName sets the "user_name" field if the given value is not nil.
+func (_u *PaymentOrderUpdate) SetNillableUserName(v *string) *PaymentOrderUpdate {
+ if v != nil {
+ _u.SetUserName(*v)
+ }
+ return _u
+}
+
+// SetUserNotes sets the "user_notes" field.
+func (_u *PaymentOrderUpdate) SetUserNotes(v string) *PaymentOrderUpdate {
+ _u.mutation.SetUserNotes(v)
+ return _u
+}
+
+// SetNillableUserNotes sets the "user_notes" field if the given value is not nil.
+func (_u *PaymentOrderUpdate) SetNillableUserNotes(v *string) *PaymentOrderUpdate {
+ if v != nil {
+ _u.SetUserNotes(*v)
+ }
+ return _u
+}
+
+// ClearUserNotes clears the value of the "user_notes" field.
+func (_u *PaymentOrderUpdate) ClearUserNotes() *PaymentOrderUpdate {
+ _u.mutation.ClearUserNotes()
+ return _u
+}
+
+// SetAmount sets the "amount" field.
+func (_u *PaymentOrderUpdate) SetAmount(v float64) *PaymentOrderUpdate {
+ _u.mutation.ResetAmount()
+ _u.mutation.SetAmount(v)
+ return _u
+}
+
+// SetNillableAmount sets the "amount" field if the given value is not nil.
+func (_u *PaymentOrderUpdate) SetNillableAmount(v *float64) *PaymentOrderUpdate {
+ if v != nil {
+ _u.SetAmount(*v)
+ }
+ return _u
+}
+
+// AddAmount adds value to the "amount" field.
+func (_u *PaymentOrderUpdate) AddAmount(v float64) *PaymentOrderUpdate {
+ _u.mutation.AddAmount(v)
+ return _u
+}
+
+// SetPayAmount sets the "pay_amount" field.
+func (_u *PaymentOrderUpdate) SetPayAmount(v float64) *PaymentOrderUpdate {
+ _u.mutation.ResetPayAmount()
+ _u.mutation.SetPayAmount(v)
+ return _u
+}
+
+// SetNillablePayAmount sets the "pay_amount" field if the given value is not nil.
+func (_u *PaymentOrderUpdate) SetNillablePayAmount(v *float64) *PaymentOrderUpdate {
+ if v != nil {
+ _u.SetPayAmount(*v)
+ }
+ return _u
+}
+
+// AddPayAmount adds value to the "pay_amount" field.
+func (_u *PaymentOrderUpdate) AddPayAmount(v float64) *PaymentOrderUpdate {
+ _u.mutation.AddPayAmount(v)
+ return _u
+}
+
+// SetFeeRate sets the "fee_rate" field.
+func (_u *PaymentOrderUpdate) SetFeeRate(v float64) *PaymentOrderUpdate {
+ _u.mutation.ResetFeeRate()
+ _u.mutation.SetFeeRate(v)
+ return _u
+}
+
+// SetNillableFeeRate sets the "fee_rate" field if the given value is not nil.
+func (_u *PaymentOrderUpdate) SetNillableFeeRate(v *float64) *PaymentOrderUpdate {
+ if v != nil {
+ _u.SetFeeRate(*v)
+ }
+ return _u
+}
+
+// AddFeeRate adds value to the "fee_rate" field.
+func (_u *PaymentOrderUpdate) AddFeeRate(v float64) *PaymentOrderUpdate {
+ _u.mutation.AddFeeRate(v)
+ return _u
+}
+
+// SetRechargeCode sets the "recharge_code" field.
+func (_u *PaymentOrderUpdate) SetRechargeCode(v string) *PaymentOrderUpdate {
+ _u.mutation.SetRechargeCode(v)
+ return _u
+}
+
+// SetNillableRechargeCode sets the "recharge_code" field if the given value is not nil.
+func (_u *PaymentOrderUpdate) SetNillableRechargeCode(v *string) *PaymentOrderUpdate {
+ if v != nil {
+ _u.SetRechargeCode(*v)
+ }
+ return _u
+}
+
+// SetOutTradeNo sets the "out_trade_no" field.
+func (_u *PaymentOrderUpdate) SetOutTradeNo(v string) *PaymentOrderUpdate {
+ _u.mutation.SetOutTradeNo(v)
+ return _u
+}
+
+// SetNillableOutTradeNo sets the "out_trade_no" field if the given value is not nil.
+func (_u *PaymentOrderUpdate) SetNillableOutTradeNo(v *string) *PaymentOrderUpdate {
+ if v != nil {
+ _u.SetOutTradeNo(*v)
+ }
+ return _u
+}
+
+// SetPaymentType sets the "payment_type" field.
+func (_u *PaymentOrderUpdate) SetPaymentType(v string) *PaymentOrderUpdate {
+ _u.mutation.SetPaymentType(v)
+ return _u
+}
+
+// SetNillablePaymentType sets the "payment_type" field if the given value is not nil.
+func (_u *PaymentOrderUpdate) SetNillablePaymentType(v *string) *PaymentOrderUpdate {
+ if v != nil {
+ _u.SetPaymentType(*v)
+ }
+ return _u
+}
+
+// SetPaymentTradeNo sets the "payment_trade_no" field.
+func (_u *PaymentOrderUpdate) SetPaymentTradeNo(v string) *PaymentOrderUpdate {
+ _u.mutation.SetPaymentTradeNo(v)
+ return _u
+}
+
+// SetNillablePaymentTradeNo sets the "payment_trade_no" field if the given value is not nil.
+func (_u *PaymentOrderUpdate) SetNillablePaymentTradeNo(v *string) *PaymentOrderUpdate {
+ if v != nil {
+ _u.SetPaymentTradeNo(*v)
+ }
+ return _u
+}
+
+// SetPayURL sets the "pay_url" field.
+func (_u *PaymentOrderUpdate) SetPayURL(v string) *PaymentOrderUpdate {
+ _u.mutation.SetPayURL(v)
+ return _u
+}
+
+// SetNillablePayURL sets the "pay_url" field if the given value is not nil.
+func (_u *PaymentOrderUpdate) SetNillablePayURL(v *string) *PaymentOrderUpdate {
+ if v != nil {
+ _u.SetPayURL(*v)
+ }
+ return _u
+}
+
+// ClearPayURL clears the value of the "pay_url" field.
+func (_u *PaymentOrderUpdate) ClearPayURL() *PaymentOrderUpdate {
+ _u.mutation.ClearPayURL()
+ return _u
+}
+
+// SetQrCode sets the "qr_code" field.
+func (_u *PaymentOrderUpdate) SetQrCode(v string) *PaymentOrderUpdate {
+ _u.mutation.SetQrCode(v)
+ return _u
+}
+
+// SetNillableQrCode sets the "qr_code" field if the given value is not nil.
+func (_u *PaymentOrderUpdate) SetNillableQrCode(v *string) *PaymentOrderUpdate {
+ if v != nil {
+ _u.SetQrCode(*v)
+ }
+ return _u
+}
+
+// ClearQrCode clears the value of the "qr_code" field.
+func (_u *PaymentOrderUpdate) ClearQrCode() *PaymentOrderUpdate {
+ _u.mutation.ClearQrCode()
+ return _u
+}
+
+// SetQrCodeImg sets the "qr_code_img" field.
+func (_u *PaymentOrderUpdate) SetQrCodeImg(v string) *PaymentOrderUpdate {
+ _u.mutation.SetQrCodeImg(v)
+ return _u
+}
+
+// SetNillableQrCodeImg sets the "qr_code_img" field if the given value is not nil.
+func (_u *PaymentOrderUpdate) SetNillableQrCodeImg(v *string) *PaymentOrderUpdate {
+ if v != nil {
+ _u.SetQrCodeImg(*v)
+ }
+ return _u
+}
+
+// ClearQrCodeImg clears the value of the "qr_code_img" field.
+func (_u *PaymentOrderUpdate) ClearQrCodeImg() *PaymentOrderUpdate {
+ _u.mutation.ClearQrCodeImg()
+ return _u
+}
+
+// SetOrderType sets the "order_type" field.
+func (_u *PaymentOrderUpdate) SetOrderType(v string) *PaymentOrderUpdate {
+ _u.mutation.SetOrderType(v)
+ return _u
+}
+
+// SetNillableOrderType sets the "order_type" field if the given value is not nil.
+func (_u *PaymentOrderUpdate) SetNillableOrderType(v *string) *PaymentOrderUpdate {
+ if v != nil {
+ _u.SetOrderType(*v)
+ }
+ return _u
+}
+
+// SetPlanID sets the "plan_id" field.
+func (_u *PaymentOrderUpdate) SetPlanID(v int64) *PaymentOrderUpdate {
+ _u.mutation.ResetPlanID()
+ _u.mutation.SetPlanID(v)
+ return _u
+}
+
+// SetNillablePlanID sets the "plan_id" field if the given value is not nil.
+func (_u *PaymentOrderUpdate) SetNillablePlanID(v *int64) *PaymentOrderUpdate {
+ if v != nil {
+ _u.SetPlanID(*v)
+ }
+ return _u
+}
+
+// AddPlanID adds value to the "plan_id" field.
+func (_u *PaymentOrderUpdate) AddPlanID(v int64) *PaymentOrderUpdate {
+ _u.mutation.AddPlanID(v)
+ return _u
+}
+
+// ClearPlanID clears the value of the "plan_id" field.
+func (_u *PaymentOrderUpdate) ClearPlanID() *PaymentOrderUpdate {
+ _u.mutation.ClearPlanID()
+ return _u
+}
+
+// SetSubscriptionGroupID sets the "subscription_group_id" field.
+func (_u *PaymentOrderUpdate) SetSubscriptionGroupID(v int64) *PaymentOrderUpdate {
+ _u.mutation.ResetSubscriptionGroupID()
+ _u.mutation.SetSubscriptionGroupID(v)
+ return _u
+}
+
+// SetNillableSubscriptionGroupID sets the "subscription_group_id" field if the given value is not nil.
+func (_u *PaymentOrderUpdate) SetNillableSubscriptionGroupID(v *int64) *PaymentOrderUpdate {
+ if v != nil {
+ _u.SetSubscriptionGroupID(*v)
+ }
+ return _u
+}
+
+// AddSubscriptionGroupID adds value to the "subscription_group_id" field.
+func (_u *PaymentOrderUpdate) AddSubscriptionGroupID(v int64) *PaymentOrderUpdate {
+ _u.mutation.AddSubscriptionGroupID(v)
+ return _u
+}
+
+// ClearSubscriptionGroupID clears the value of the "subscription_group_id" field.
+func (_u *PaymentOrderUpdate) ClearSubscriptionGroupID() *PaymentOrderUpdate {
+ _u.mutation.ClearSubscriptionGroupID()
+ return _u
+}
+
+// SetSubscriptionDays sets the "subscription_days" field.
+func (_u *PaymentOrderUpdate) SetSubscriptionDays(v int) *PaymentOrderUpdate {
+ _u.mutation.ResetSubscriptionDays()
+ _u.mutation.SetSubscriptionDays(v)
+ return _u
+}
+
+// SetNillableSubscriptionDays sets the "subscription_days" field if the given value is not nil.
+func (_u *PaymentOrderUpdate) SetNillableSubscriptionDays(v *int) *PaymentOrderUpdate {
+ if v != nil {
+ _u.SetSubscriptionDays(*v)
+ }
+ return _u
+}
+
+// AddSubscriptionDays adds value to the "subscription_days" field.
+func (_u *PaymentOrderUpdate) AddSubscriptionDays(v int) *PaymentOrderUpdate {
+ _u.mutation.AddSubscriptionDays(v)
+ return _u
+}
+
+// ClearSubscriptionDays clears the value of the "subscription_days" field.
+func (_u *PaymentOrderUpdate) ClearSubscriptionDays() *PaymentOrderUpdate {
+ _u.mutation.ClearSubscriptionDays()
+ return _u
+}
+
+// SetProviderInstanceID sets the "provider_instance_id" field.
+func (_u *PaymentOrderUpdate) SetProviderInstanceID(v string) *PaymentOrderUpdate {
+ _u.mutation.SetProviderInstanceID(v)
+ return _u
+}
+
+// SetNillableProviderInstanceID sets the "provider_instance_id" field if the given value is not nil.
+func (_u *PaymentOrderUpdate) SetNillableProviderInstanceID(v *string) *PaymentOrderUpdate {
+ if v != nil {
+ _u.SetProviderInstanceID(*v)
+ }
+ return _u
+}
+
+// ClearProviderInstanceID clears the value of the "provider_instance_id" field.
+func (_u *PaymentOrderUpdate) ClearProviderInstanceID() *PaymentOrderUpdate {
+ _u.mutation.ClearProviderInstanceID()
+ return _u
+}
+
+// SetStatus sets the "status" field.
+func (_u *PaymentOrderUpdate) SetStatus(v string) *PaymentOrderUpdate {
+ _u.mutation.SetStatus(v)
+ return _u
+}
+
+// SetNillableStatus sets the "status" field if the given value is not nil.
+func (_u *PaymentOrderUpdate) SetNillableStatus(v *string) *PaymentOrderUpdate {
+ if v != nil {
+ _u.SetStatus(*v)
+ }
+ return _u
+}
+
+// SetRefundAmount sets the "refund_amount" field.
+func (_u *PaymentOrderUpdate) SetRefundAmount(v float64) *PaymentOrderUpdate {
+ _u.mutation.ResetRefundAmount()
+ _u.mutation.SetRefundAmount(v)
+ return _u
+}
+
+// SetNillableRefundAmount sets the "refund_amount" field if the given value is not nil.
+func (_u *PaymentOrderUpdate) SetNillableRefundAmount(v *float64) *PaymentOrderUpdate {
+ if v != nil {
+ _u.SetRefundAmount(*v)
+ }
+ return _u
+}
+
+// AddRefundAmount adds value to the "refund_amount" field.
+func (_u *PaymentOrderUpdate) AddRefundAmount(v float64) *PaymentOrderUpdate {
+ _u.mutation.AddRefundAmount(v)
+ return _u
+}
+
+// SetRefundReason sets the "refund_reason" field.
+func (_u *PaymentOrderUpdate) SetRefundReason(v string) *PaymentOrderUpdate {
+ _u.mutation.SetRefundReason(v)
+ return _u
+}
+
+// SetNillableRefundReason sets the "refund_reason" field if the given value is not nil.
+func (_u *PaymentOrderUpdate) SetNillableRefundReason(v *string) *PaymentOrderUpdate {
+ if v != nil {
+ _u.SetRefundReason(*v)
+ }
+ return _u
+}
+
+// ClearRefundReason clears the value of the "refund_reason" field.
+func (_u *PaymentOrderUpdate) ClearRefundReason() *PaymentOrderUpdate {
+ _u.mutation.ClearRefundReason()
+ return _u
+}
+
+// SetRefundAt sets the "refund_at" field.
+func (_u *PaymentOrderUpdate) SetRefundAt(v time.Time) *PaymentOrderUpdate {
+ _u.mutation.SetRefundAt(v)
+ return _u
+}
+
+// SetNillableRefundAt sets the "refund_at" field if the given value is not nil.
+func (_u *PaymentOrderUpdate) SetNillableRefundAt(v *time.Time) *PaymentOrderUpdate {
+ if v != nil {
+ _u.SetRefundAt(*v)
+ }
+ return _u
+}
+
+// ClearRefundAt clears the value of the "refund_at" field.
+func (_u *PaymentOrderUpdate) ClearRefundAt() *PaymentOrderUpdate {
+ _u.mutation.ClearRefundAt()
+ return _u
+}
+
+// SetForceRefund sets the "force_refund" field.
+func (_u *PaymentOrderUpdate) SetForceRefund(v bool) *PaymentOrderUpdate {
+ _u.mutation.SetForceRefund(v)
+ return _u
+}
+
+// SetNillableForceRefund sets the "force_refund" field if the given value is not nil.
+func (_u *PaymentOrderUpdate) SetNillableForceRefund(v *bool) *PaymentOrderUpdate {
+ if v != nil {
+ _u.SetForceRefund(*v)
+ }
+ return _u
+}
+
+// SetRefundRequestedAt sets the "refund_requested_at" field.
+func (_u *PaymentOrderUpdate) SetRefundRequestedAt(v time.Time) *PaymentOrderUpdate {
+ _u.mutation.SetRefundRequestedAt(v)
+ return _u
+}
+
+// SetNillableRefundRequestedAt sets the "refund_requested_at" field if the given value is not nil.
+func (_u *PaymentOrderUpdate) SetNillableRefundRequestedAt(v *time.Time) *PaymentOrderUpdate {
+ if v != nil {
+ _u.SetRefundRequestedAt(*v)
+ }
+ return _u
+}
+
+// ClearRefundRequestedAt clears the value of the "refund_requested_at" field.
+func (_u *PaymentOrderUpdate) ClearRefundRequestedAt() *PaymentOrderUpdate {
+ _u.mutation.ClearRefundRequestedAt()
+ return _u
+}
+
+// SetRefundRequestReason sets the "refund_request_reason" field.
+func (_u *PaymentOrderUpdate) SetRefundRequestReason(v string) *PaymentOrderUpdate {
+ _u.mutation.SetRefundRequestReason(v)
+ return _u
+}
+
+// SetNillableRefundRequestReason sets the "refund_request_reason" field if the given value is not nil.
+func (_u *PaymentOrderUpdate) SetNillableRefundRequestReason(v *string) *PaymentOrderUpdate {
+ if v != nil {
+ _u.SetRefundRequestReason(*v)
+ }
+ return _u
+}
+
+// ClearRefundRequestReason clears the value of the "refund_request_reason" field.
+func (_u *PaymentOrderUpdate) ClearRefundRequestReason() *PaymentOrderUpdate {
+ _u.mutation.ClearRefundRequestReason()
+ return _u
+}
+
+// SetRefundRequestedBy sets the "refund_requested_by" field.
+func (_u *PaymentOrderUpdate) SetRefundRequestedBy(v string) *PaymentOrderUpdate {
+ _u.mutation.SetRefundRequestedBy(v)
+ return _u
+}
+
+// SetNillableRefundRequestedBy sets the "refund_requested_by" field if the given value is not nil.
+func (_u *PaymentOrderUpdate) SetNillableRefundRequestedBy(v *string) *PaymentOrderUpdate {
+ if v != nil {
+ _u.SetRefundRequestedBy(*v)
+ }
+ return _u
+}
+
+// ClearRefundRequestedBy clears the value of the "refund_requested_by" field.
+func (_u *PaymentOrderUpdate) ClearRefundRequestedBy() *PaymentOrderUpdate {
+ _u.mutation.ClearRefundRequestedBy()
+ return _u
+}
+
+// SetExpiresAt sets the "expires_at" field.
+func (_u *PaymentOrderUpdate) SetExpiresAt(v time.Time) *PaymentOrderUpdate {
+ _u.mutation.SetExpiresAt(v)
+ return _u
+}
+
+// SetNillableExpiresAt sets the "expires_at" field if the given value is not nil.
+func (_u *PaymentOrderUpdate) SetNillableExpiresAt(v *time.Time) *PaymentOrderUpdate {
+ if v != nil {
+ _u.SetExpiresAt(*v)
+ }
+ return _u
+}
+
+// SetPaidAt sets the "paid_at" field.
+func (_u *PaymentOrderUpdate) SetPaidAt(v time.Time) *PaymentOrderUpdate {
+ _u.mutation.SetPaidAt(v)
+ return _u
+}
+
+// SetNillablePaidAt sets the "paid_at" field if the given value is not nil.
+func (_u *PaymentOrderUpdate) SetNillablePaidAt(v *time.Time) *PaymentOrderUpdate {
+ if v != nil {
+ _u.SetPaidAt(*v)
+ }
+ return _u
+}
+
+// ClearPaidAt clears the value of the "paid_at" field.
+func (_u *PaymentOrderUpdate) ClearPaidAt() *PaymentOrderUpdate {
+ _u.mutation.ClearPaidAt()
+ return _u
+}
+
+// SetCompletedAt sets the "completed_at" field.
+func (_u *PaymentOrderUpdate) SetCompletedAt(v time.Time) *PaymentOrderUpdate {
+ _u.mutation.SetCompletedAt(v)
+ return _u
+}
+
+// SetNillableCompletedAt sets the "completed_at" field if the given value is not nil.
+func (_u *PaymentOrderUpdate) SetNillableCompletedAt(v *time.Time) *PaymentOrderUpdate {
+ if v != nil {
+ _u.SetCompletedAt(*v)
+ }
+ return _u
+}
+
+// ClearCompletedAt clears the value of the "completed_at" field.
+func (_u *PaymentOrderUpdate) ClearCompletedAt() *PaymentOrderUpdate {
+ _u.mutation.ClearCompletedAt()
+ return _u
+}
+
+// SetFailedAt sets the "failed_at" field.
+func (_u *PaymentOrderUpdate) SetFailedAt(v time.Time) *PaymentOrderUpdate {
+ _u.mutation.SetFailedAt(v)
+ return _u
+}
+
+// SetNillableFailedAt sets the "failed_at" field if the given value is not nil.
+func (_u *PaymentOrderUpdate) SetNillableFailedAt(v *time.Time) *PaymentOrderUpdate {
+ if v != nil {
+ _u.SetFailedAt(*v)
+ }
+ return _u
+}
+
+// ClearFailedAt clears the value of the "failed_at" field.
+func (_u *PaymentOrderUpdate) ClearFailedAt() *PaymentOrderUpdate {
+ _u.mutation.ClearFailedAt()
+ return _u
+}
+
+// SetFailedReason sets the "failed_reason" field.
+func (_u *PaymentOrderUpdate) SetFailedReason(v string) *PaymentOrderUpdate {
+ _u.mutation.SetFailedReason(v)
+ return _u
+}
+
+// SetNillableFailedReason sets the "failed_reason" field if the given value is not nil.
+func (_u *PaymentOrderUpdate) SetNillableFailedReason(v *string) *PaymentOrderUpdate {
+ if v != nil {
+ _u.SetFailedReason(*v)
+ }
+ return _u
+}
+
+// ClearFailedReason clears the value of the "failed_reason" field.
+func (_u *PaymentOrderUpdate) ClearFailedReason() *PaymentOrderUpdate {
+ _u.mutation.ClearFailedReason()
+ return _u
+}
+
+// SetClientIP sets the "client_ip" field.
+func (_u *PaymentOrderUpdate) SetClientIP(v string) *PaymentOrderUpdate {
+ _u.mutation.SetClientIP(v)
+ return _u
+}
+
+// SetNillableClientIP sets the "client_ip" field if the given value is not nil.
+func (_u *PaymentOrderUpdate) SetNillableClientIP(v *string) *PaymentOrderUpdate {
+ if v != nil {
+ _u.SetClientIP(*v)
+ }
+ return _u
+}
+
+// SetSrcHost sets the "src_host" field.
+func (_u *PaymentOrderUpdate) SetSrcHost(v string) *PaymentOrderUpdate {
+ _u.mutation.SetSrcHost(v)
+ return _u
+}
+
+// SetNillableSrcHost sets the "src_host" field if the given value is not nil.
+func (_u *PaymentOrderUpdate) SetNillableSrcHost(v *string) *PaymentOrderUpdate {
+ if v != nil {
+ _u.SetSrcHost(*v)
+ }
+ return _u
+}
+
+// SetSrcURL sets the "src_url" field.
+func (_u *PaymentOrderUpdate) SetSrcURL(v string) *PaymentOrderUpdate {
+ _u.mutation.SetSrcURL(v)
+ return _u
+}
+
+// SetNillableSrcURL sets the "src_url" field if the given value is not nil.
+func (_u *PaymentOrderUpdate) SetNillableSrcURL(v *string) *PaymentOrderUpdate {
+ if v != nil {
+ _u.SetSrcURL(*v)
+ }
+ return _u
+}
+
+// ClearSrcURL clears the value of the "src_url" field.
+func (_u *PaymentOrderUpdate) ClearSrcURL() *PaymentOrderUpdate {
+ _u.mutation.ClearSrcURL()
+ return _u
+}
+
+// SetUpdatedAt sets the "updated_at" field.
+func (_u *PaymentOrderUpdate) SetUpdatedAt(v time.Time) *PaymentOrderUpdate {
+ _u.mutation.SetUpdatedAt(v)
+ return _u
+}
+
+// SetUser sets the "user" edge to the User entity.
+func (_u *PaymentOrderUpdate) SetUser(v *User) *PaymentOrderUpdate {
+ return _u.SetUserID(v.ID)
+}
+
+// Mutation returns the PaymentOrderMutation object of the builder.
+func (_u *PaymentOrderUpdate) Mutation() *PaymentOrderMutation {
+ return _u.mutation
+}
+
+// ClearUser clears the "user" edge to the User entity.
+func (_u *PaymentOrderUpdate) ClearUser() *PaymentOrderUpdate {
+ _u.mutation.ClearUser()
+ return _u
+}
+
+// Save executes the query and returns the number of nodes affected by the update operation.
+func (_u *PaymentOrderUpdate) 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 *PaymentOrderUpdate) SaveX(ctx context.Context) int {
+ affected, err := _u.Save(ctx)
+ if err != nil {
+ panic(err)
+ }
+ return affected
+}
+
+// Exec executes the query.
+func (_u *PaymentOrderUpdate) Exec(ctx context.Context) error {
+ _, err := _u.Save(ctx)
+ return err
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (_u *PaymentOrderUpdate) 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 *PaymentOrderUpdate) defaults() {
+ if _, ok := _u.mutation.UpdatedAt(); !ok {
+ v := paymentorder.UpdateDefaultUpdatedAt()
+ _u.mutation.SetUpdatedAt(v)
+ }
+}
+
+// check runs all checks and user-defined validators on the builder.
+func (_u *PaymentOrderUpdate) check() error {
+ if v, ok := _u.mutation.UserEmail(); ok {
+ if err := paymentorder.UserEmailValidator(v); err != nil {
+ return &ValidationError{Name: "user_email", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.user_email": %w`, err)}
+ }
+ }
+ if v, ok := _u.mutation.UserName(); ok {
+ if err := paymentorder.UserNameValidator(v); err != nil {
+ return &ValidationError{Name: "user_name", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.user_name": %w`, err)}
+ }
+ }
+ if v, ok := _u.mutation.RechargeCode(); ok {
+ if err := paymentorder.RechargeCodeValidator(v); err != nil {
+ return &ValidationError{Name: "recharge_code", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.recharge_code": %w`, err)}
+ }
+ }
+ if v, ok := _u.mutation.OutTradeNo(); ok {
+ if err := paymentorder.OutTradeNoValidator(v); err != nil {
+ return &ValidationError{Name: "out_trade_no", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.out_trade_no": %w`, err)}
+ }
+ }
+ if v, ok := _u.mutation.PaymentType(); ok {
+ if err := paymentorder.PaymentTypeValidator(v); err != nil {
+ return &ValidationError{Name: "payment_type", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.payment_type": %w`, err)}
+ }
+ }
+ if v, ok := _u.mutation.PaymentTradeNo(); ok {
+ if err := paymentorder.PaymentTradeNoValidator(v); err != nil {
+ return &ValidationError{Name: "payment_trade_no", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.payment_trade_no": %w`, err)}
+ }
+ }
+ if v, ok := _u.mutation.OrderType(); ok {
+ if err := paymentorder.OrderTypeValidator(v); err != nil {
+ return &ValidationError{Name: "order_type", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.order_type": %w`, err)}
+ }
+ }
+ if v, ok := _u.mutation.ProviderInstanceID(); ok {
+ if err := paymentorder.ProviderInstanceIDValidator(v); err != nil {
+ return &ValidationError{Name: "provider_instance_id", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.provider_instance_id": %w`, err)}
+ }
+ }
+ if v, ok := _u.mutation.Status(); ok {
+ if err := paymentorder.StatusValidator(v); err != nil {
+ return &ValidationError{Name: "status", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.status": %w`, err)}
+ }
+ }
+ if v, ok := _u.mutation.RefundRequestedBy(); ok {
+ if err := paymentorder.RefundRequestedByValidator(v); err != nil {
+ return &ValidationError{Name: "refund_requested_by", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.refund_requested_by": %w`, err)}
+ }
+ }
+ if v, ok := _u.mutation.ClientIP(); ok {
+ if err := paymentorder.ClientIPValidator(v); err != nil {
+ return &ValidationError{Name: "client_ip", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.client_ip": %w`, err)}
+ }
+ }
+ if v, ok := _u.mutation.SrcHost(); ok {
+ if err := paymentorder.SrcHostValidator(v); err != nil {
+ return &ValidationError{Name: "src_host", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.src_host": %w`, err)}
+ }
+ }
+ if _u.mutation.UserCleared() && len(_u.mutation.UserIDs()) > 0 {
+ return errors.New(`ent: clearing a required unique edge "PaymentOrder.user"`)
+ }
+ return nil
+}
+
+func (_u *PaymentOrderUpdate) sqlSave(ctx context.Context) (_node int, err error) {
+ if err := _u.check(); err != nil {
+ return _node, err
+ }
+ _spec := sqlgraph.NewUpdateSpec(paymentorder.Table, paymentorder.Columns, sqlgraph.NewFieldSpec(paymentorder.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.UserEmail(); ok {
+ _spec.SetField(paymentorder.FieldUserEmail, field.TypeString, value)
+ }
+ if value, ok := _u.mutation.UserName(); ok {
+ _spec.SetField(paymentorder.FieldUserName, field.TypeString, value)
+ }
+ if value, ok := _u.mutation.UserNotes(); ok {
+ _spec.SetField(paymentorder.FieldUserNotes, field.TypeString, value)
+ }
+ if _u.mutation.UserNotesCleared() {
+ _spec.ClearField(paymentorder.FieldUserNotes, field.TypeString)
+ }
+ if value, ok := _u.mutation.Amount(); ok {
+ _spec.SetField(paymentorder.FieldAmount, field.TypeFloat64, value)
+ }
+ if value, ok := _u.mutation.AddedAmount(); ok {
+ _spec.AddField(paymentorder.FieldAmount, field.TypeFloat64, value)
+ }
+ if value, ok := _u.mutation.PayAmount(); ok {
+ _spec.SetField(paymentorder.FieldPayAmount, field.TypeFloat64, value)
+ }
+ if value, ok := _u.mutation.AddedPayAmount(); ok {
+ _spec.AddField(paymentorder.FieldPayAmount, field.TypeFloat64, value)
+ }
+ if value, ok := _u.mutation.FeeRate(); ok {
+ _spec.SetField(paymentorder.FieldFeeRate, field.TypeFloat64, value)
+ }
+ if value, ok := _u.mutation.AddedFeeRate(); ok {
+ _spec.AddField(paymentorder.FieldFeeRate, field.TypeFloat64, value)
+ }
+ if value, ok := _u.mutation.RechargeCode(); ok {
+ _spec.SetField(paymentorder.FieldRechargeCode, field.TypeString, value)
+ }
+ if value, ok := _u.mutation.OutTradeNo(); ok {
+ _spec.SetField(paymentorder.FieldOutTradeNo, field.TypeString, value)
+ }
+ if value, ok := _u.mutation.PaymentType(); ok {
+ _spec.SetField(paymentorder.FieldPaymentType, field.TypeString, value)
+ }
+ if value, ok := _u.mutation.PaymentTradeNo(); ok {
+ _spec.SetField(paymentorder.FieldPaymentTradeNo, field.TypeString, value)
+ }
+ if value, ok := _u.mutation.PayURL(); ok {
+ _spec.SetField(paymentorder.FieldPayURL, field.TypeString, value)
+ }
+ if _u.mutation.PayURLCleared() {
+ _spec.ClearField(paymentorder.FieldPayURL, field.TypeString)
+ }
+ if value, ok := _u.mutation.QrCode(); ok {
+ _spec.SetField(paymentorder.FieldQrCode, field.TypeString, value)
+ }
+ if _u.mutation.QrCodeCleared() {
+ _spec.ClearField(paymentorder.FieldQrCode, field.TypeString)
+ }
+ if value, ok := _u.mutation.QrCodeImg(); ok {
+ _spec.SetField(paymentorder.FieldQrCodeImg, field.TypeString, value)
+ }
+ if _u.mutation.QrCodeImgCleared() {
+ _spec.ClearField(paymentorder.FieldQrCodeImg, field.TypeString)
+ }
+ if value, ok := _u.mutation.OrderType(); ok {
+ _spec.SetField(paymentorder.FieldOrderType, field.TypeString, value)
+ }
+ if value, ok := _u.mutation.PlanID(); ok {
+ _spec.SetField(paymentorder.FieldPlanID, field.TypeInt64, value)
+ }
+ if value, ok := _u.mutation.AddedPlanID(); ok {
+ _spec.AddField(paymentorder.FieldPlanID, field.TypeInt64, value)
+ }
+ if _u.mutation.PlanIDCleared() {
+ _spec.ClearField(paymentorder.FieldPlanID, field.TypeInt64)
+ }
+ if value, ok := _u.mutation.SubscriptionGroupID(); ok {
+ _spec.SetField(paymentorder.FieldSubscriptionGroupID, field.TypeInt64, value)
+ }
+ if value, ok := _u.mutation.AddedSubscriptionGroupID(); ok {
+ _spec.AddField(paymentorder.FieldSubscriptionGroupID, field.TypeInt64, value)
+ }
+ if _u.mutation.SubscriptionGroupIDCleared() {
+ _spec.ClearField(paymentorder.FieldSubscriptionGroupID, field.TypeInt64)
+ }
+ if value, ok := _u.mutation.SubscriptionDays(); ok {
+ _spec.SetField(paymentorder.FieldSubscriptionDays, field.TypeInt, value)
+ }
+ if value, ok := _u.mutation.AddedSubscriptionDays(); ok {
+ _spec.AddField(paymentorder.FieldSubscriptionDays, field.TypeInt, value)
+ }
+ if _u.mutation.SubscriptionDaysCleared() {
+ _spec.ClearField(paymentorder.FieldSubscriptionDays, field.TypeInt)
+ }
+ if value, ok := _u.mutation.ProviderInstanceID(); ok {
+ _spec.SetField(paymentorder.FieldProviderInstanceID, field.TypeString, value)
+ }
+ if _u.mutation.ProviderInstanceIDCleared() {
+ _spec.ClearField(paymentorder.FieldProviderInstanceID, field.TypeString)
+ }
+ if value, ok := _u.mutation.Status(); ok {
+ _spec.SetField(paymentorder.FieldStatus, field.TypeString, value)
+ }
+ if value, ok := _u.mutation.RefundAmount(); ok {
+ _spec.SetField(paymentorder.FieldRefundAmount, field.TypeFloat64, value)
+ }
+ if value, ok := _u.mutation.AddedRefundAmount(); ok {
+ _spec.AddField(paymentorder.FieldRefundAmount, field.TypeFloat64, value)
+ }
+ if value, ok := _u.mutation.RefundReason(); ok {
+ _spec.SetField(paymentorder.FieldRefundReason, field.TypeString, value)
+ }
+ if _u.mutation.RefundReasonCleared() {
+ _spec.ClearField(paymentorder.FieldRefundReason, field.TypeString)
+ }
+ if value, ok := _u.mutation.RefundAt(); ok {
+ _spec.SetField(paymentorder.FieldRefundAt, field.TypeTime, value)
+ }
+ if _u.mutation.RefundAtCleared() {
+ _spec.ClearField(paymentorder.FieldRefundAt, field.TypeTime)
+ }
+ if value, ok := _u.mutation.ForceRefund(); ok {
+ _spec.SetField(paymentorder.FieldForceRefund, field.TypeBool, value)
+ }
+ if value, ok := _u.mutation.RefundRequestedAt(); ok {
+ _spec.SetField(paymentorder.FieldRefundRequestedAt, field.TypeTime, value)
+ }
+ if _u.mutation.RefundRequestedAtCleared() {
+ _spec.ClearField(paymentorder.FieldRefundRequestedAt, field.TypeTime)
+ }
+ if value, ok := _u.mutation.RefundRequestReason(); ok {
+ _spec.SetField(paymentorder.FieldRefundRequestReason, field.TypeString, value)
+ }
+ if _u.mutation.RefundRequestReasonCleared() {
+ _spec.ClearField(paymentorder.FieldRefundRequestReason, field.TypeString)
+ }
+ if value, ok := _u.mutation.RefundRequestedBy(); ok {
+ _spec.SetField(paymentorder.FieldRefundRequestedBy, field.TypeString, value)
+ }
+ if _u.mutation.RefundRequestedByCleared() {
+ _spec.ClearField(paymentorder.FieldRefundRequestedBy, field.TypeString)
+ }
+ if value, ok := _u.mutation.ExpiresAt(); ok {
+ _spec.SetField(paymentorder.FieldExpiresAt, field.TypeTime, value)
+ }
+ if value, ok := _u.mutation.PaidAt(); ok {
+ _spec.SetField(paymentorder.FieldPaidAt, field.TypeTime, value)
+ }
+ if _u.mutation.PaidAtCleared() {
+ _spec.ClearField(paymentorder.FieldPaidAt, field.TypeTime)
+ }
+ if value, ok := _u.mutation.CompletedAt(); ok {
+ _spec.SetField(paymentorder.FieldCompletedAt, field.TypeTime, value)
+ }
+ if _u.mutation.CompletedAtCleared() {
+ _spec.ClearField(paymentorder.FieldCompletedAt, field.TypeTime)
+ }
+ if value, ok := _u.mutation.FailedAt(); ok {
+ _spec.SetField(paymentorder.FieldFailedAt, field.TypeTime, value)
+ }
+ if _u.mutation.FailedAtCleared() {
+ _spec.ClearField(paymentorder.FieldFailedAt, field.TypeTime)
+ }
+ if value, ok := _u.mutation.FailedReason(); ok {
+ _spec.SetField(paymentorder.FieldFailedReason, field.TypeString, value)
+ }
+ if _u.mutation.FailedReasonCleared() {
+ _spec.ClearField(paymentorder.FieldFailedReason, field.TypeString)
+ }
+ if value, ok := _u.mutation.ClientIP(); ok {
+ _spec.SetField(paymentorder.FieldClientIP, field.TypeString, value)
+ }
+ if value, ok := _u.mutation.SrcHost(); ok {
+ _spec.SetField(paymentorder.FieldSrcHost, field.TypeString, value)
+ }
+ if value, ok := _u.mutation.SrcURL(); ok {
+ _spec.SetField(paymentorder.FieldSrcURL, field.TypeString, value)
+ }
+ if _u.mutation.SrcURLCleared() {
+ _spec.ClearField(paymentorder.FieldSrcURL, field.TypeString)
+ }
+ if value, ok := _u.mutation.UpdatedAt(); ok {
+ _spec.SetField(paymentorder.FieldUpdatedAt, field.TypeTime, value)
+ }
+ if _u.mutation.UserCleared() {
+ edge := &sqlgraph.EdgeSpec{
+ Rel: sqlgraph.M2O,
+ Inverse: true,
+ Table: paymentorder.UserTable,
+ Columns: []string{paymentorder.UserColumn},
+ Bidi: false,
+ Target: &sqlgraph.EdgeTarget{
+ IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt64),
+ },
+ }
+ _spec.Edges.Clear = append(_spec.Edges.Clear, edge)
+ }
+ if nodes := _u.mutation.UserIDs(); len(nodes) > 0 {
+ edge := &sqlgraph.EdgeSpec{
+ Rel: sqlgraph.M2O,
+ Inverse: true,
+ Table: paymentorder.UserTable,
+ Columns: []string{paymentorder.UserColumn},
+ Bidi: false,
+ Target: &sqlgraph.EdgeTarget{
+ IDSpec: sqlgraph.NewFieldSpec(user.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{paymentorder.Label}
+ } else if sqlgraph.IsConstraintError(err) {
+ err = &ConstraintError{msg: err.Error(), wrap: err}
+ }
+ return 0, err
+ }
+ _u.mutation.done = true
+ return _node, nil
+}
+
+// PaymentOrderUpdateOne is the builder for updating a single PaymentOrder entity.
+type PaymentOrderUpdateOne struct {
+ config
+ fields []string
+ hooks []Hook
+ mutation *PaymentOrderMutation
+}
+
+// SetUserID sets the "user_id" field.
+func (_u *PaymentOrderUpdateOne) SetUserID(v int64) *PaymentOrderUpdateOne {
+ _u.mutation.SetUserID(v)
+ return _u
+}
+
+// SetNillableUserID sets the "user_id" field if the given value is not nil.
+func (_u *PaymentOrderUpdateOne) SetNillableUserID(v *int64) *PaymentOrderUpdateOne {
+ if v != nil {
+ _u.SetUserID(*v)
+ }
+ return _u
+}
+
+// SetUserEmail sets the "user_email" field.
+func (_u *PaymentOrderUpdateOne) SetUserEmail(v string) *PaymentOrderUpdateOne {
+ _u.mutation.SetUserEmail(v)
+ return _u
+}
+
+// SetNillableUserEmail sets the "user_email" field if the given value is not nil.
+func (_u *PaymentOrderUpdateOne) SetNillableUserEmail(v *string) *PaymentOrderUpdateOne {
+ if v != nil {
+ _u.SetUserEmail(*v)
+ }
+ return _u
+}
+
+// SetUserName sets the "user_name" field.
+func (_u *PaymentOrderUpdateOne) SetUserName(v string) *PaymentOrderUpdateOne {
+ _u.mutation.SetUserName(v)
+ return _u
+}
+
+// SetNillableUserName sets the "user_name" field if the given value is not nil.
+func (_u *PaymentOrderUpdateOne) SetNillableUserName(v *string) *PaymentOrderUpdateOne {
+ if v != nil {
+ _u.SetUserName(*v)
+ }
+ return _u
+}
+
+// SetUserNotes sets the "user_notes" field.
+func (_u *PaymentOrderUpdateOne) SetUserNotes(v string) *PaymentOrderUpdateOne {
+ _u.mutation.SetUserNotes(v)
+ return _u
+}
+
+// SetNillableUserNotes sets the "user_notes" field if the given value is not nil.
+func (_u *PaymentOrderUpdateOne) SetNillableUserNotes(v *string) *PaymentOrderUpdateOne {
+ if v != nil {
+ _u.SetUserNotes(*v)
+ }
+ return _u
+}
+
+// ClearUserNotes clears the value of the "user_notes" field.
+func (_u *PaymentOrderUpdateOne) ClearUserNotes() *PaymentOrderUpdateOne {
+ _u.mutation.ClearUserNotes()
+ return _u
+}
+
+// SetAmount sets the "amount" field.
+func (_u *PaymentOrderUpdateOne) SetAmount(v float64) *PaymentOrderUpdateOne {
+ _u.mutation.ResetAmount()
+ _u.mutation.SetAmount(v)
+ return _u
+}
+
+// SetNillableAmount sets the "amount" field if the given value is not nil.
+func (_u *PaymentOrderUpdateOne) SetNillableAmount(v *float64) *PaymentOrderUpdateOne {
+ if v != nil {
+ _u.SetAmount(*v)
+ }
+ return _u
+}
+
+// AddAmount adds value to the "amount" field.
+func (_u *PaymentOrderUpdateOne) AddAmount(v float64) *PaymentOrderUpdateOne {
+ _u.mutation.AddAmount(v)
+ return _u
+}
+
+// SetPayAmount sets the "pay_amount" field.
+func (_u *PaymentOrderUpdateOne) SetPayAmount(v float64) *PaymentOrderUpdateOne {
+ _u.mutation.ResetPayAmount()
+ _u.mutation.SetPayAmount(v)
+ return _u
+}
+
+// SetNillablePayAmount sets the "pay_amount" field if the given value is not nil.
+func (_u *PaymentOrderUpdateOne) SetNillablePayAmount(v *float64) *PaymentOrderUpdateOne {
+ if v != nil {
+ _u.SetPayAmount(*v)
+ }
+ return _u
+}
+
+// AddPayAmount adds value to the "pay_amount" field.
+func (_u *PaymentOrderUpdateOne) AddPayAmount(v float64) *PaymentOrderUpdateOne {
+ _u.mutation.AddPayAmount(v)
+ return _u
+}
+
+// SetFeeRate sets the "fee_rate" field.
+func (_u *PaymentOrderUpdateOne) SetFeeRate(v float64) *PaymentOrderUpdateOne {
+ _u.mutation.ResetFeeRate()
+ _u.mutation.SetFeeRate(v)
+ return _u
+}
+
+// SetNillableFeeRate sets the "fee_rate" field if the given value is not nil.
+func (_u *PaymentOrderUpdateOne) SetNillableFeeRate(v *float64) *PaymentOrderUpdateOne {
+ if v != nil {
+ _u.SetFeeRate(*v)
+ }
+ return _u
+}
+
+// AddFeeRate adds value to the "fee_rate" field.
+func (_u *PaymentOrderUpdateOne) AddFeeRate(v float64) *PaymentOrderUpdateOne {
+ _u.mutation.AddFeeRate(v)
+ return _u
+}
+
+// SetRechargeCode sets the "recharge_code" field.
+func (_u *PaymentOrderUpdateOne) SetRechargeCode(v string) *PaymentOrderUpdateOne {
+ _u.mutation.SetRechargeCode(v)
+ return _u
+}
+
+// SetNillableRechargeCode sets the "recharge_code" field if the given value is not nil.
+func (_u *PaymentOrderUpdateOne) SetNillableRechargeCode(v *string) *PaymentOrderUpdateOne {
+ if v != nil {
+ _u.SetRechargeCode(*v)
+ }
+ return _u
+}
+
+// SetOutTradeNo sets the "out_trade_no" field.
+func (_u *PaymentOrderUpdateOne) SetOutTradeNo(v string) *PaymentOrderUpdateOne {
+ _u.mutation.SetOutTradeNo(v)
+ return _u
+}
+
+// SetNillableOutTradeNo sets the "out_trade_no" field if the given value is not nil.
+func (_u *PaymentOrderUpdateOne) SetNillableOutTradeNo(v *string) *PaymentOrderUpdateOne {
+ if v != nil {
+ _u.SetOutTradeNo(*v)
+ }
+ return _u
+}
+
+// SetPaymentType sets the "payment_type" field.
+func (_u *PaymentOrderUpdateOne) SetPaymentType(v string) *PaymentOrderUpdateOne {
+ _u.mutation.SetPaymentType(v)
+ return _u
+}
+
+// SetNillablePaymentType sets the "payment_type" field if the given value is not nil.
+func (_u *PaymentOrderUpdateOne) SetNillablePaymentType(v *string) *PaymentOrderUpdateOne {
+ if v != nil {
+ _u.SetPaymentType(*v)
+ }
+ return _u
+}
+
+// SetPaymentTradeNo sets the "payment_trade_no" field.
+func (_u *PaymentOrderUpdateOne) SetPaymentTradeNo(v string) *PaymentOrderUpdateOne {
+ _u.mutation.SetPaymentTradeNo(v)
+ return _u
+}
+
+// SetNillablePaymentTradeNo sets the "payment_trade_no" field if the given value is not nil.
+func (_u *PaymentOrderUpdateOne) SetNillablePaymentTradeNo(v *string) *PaymentOrderUpdateOne {
+ if v != nil {
+ _u.SetPaymentTradeNo(*v)
+ }
+ return _u
+}
+
+// SetPayURL sets the "pay_url" field.
+func (_u *PaymentOrderUpdateOne) SetPayURL(v string) *PaymentOrderUpdateOne {
+ _u.mutation.SetPayURL(v)
+ return _u
+}
+
+// SetNillablePayURL sets the "pay_url" field if the given value is not nil.
+func (_u *PaymentOrderUpdateOne) SetNillablePayURL(v *string) *PaymentOrderUpdateOne {
+ if v != nil {
+ _u.SetPayURL(*v)
+ }
+ return _u
+}
+
+// ClearPayURL clears the value of the "pay_url" field.
+func (_u *PaymentOrderUpdateOne) ClearPayURL() *PaymentOrderUpdateOne {
+ _u.mutation.ClearPayURL()
+ return _u
+}
+
+// SetQrCode sets the "qr_code" field.
+func (_u *PaymentOrderUpdateOne) SetQrCode(v string) *PaymentOrderUpdateOne {
+ _u.mutation.SetQrCode(v)
+ return _u
+}
+
+// SetNillableQrCode sets the "qr_code" field if the given value is not nil.
+func (_u *PaymentOrderUpdateOne) SetNillableQrCode(v *string) *PaymentOrderUpdateOne {
+ if v != nil {
+ _u.SetQrCode(*v)
+ }
+ return _u
+}
+
+// ClearQrCode clears the value of the "qr_code" field.
+func (_u *PaymentOrderUpdateOne) ClearQrCode() *PaymentOrderUpdateOne {
+ _u.mutation.ClearQrCode()
+ return _u
+}
+
+// SetQrCodeImg sets the "qr_code_img" field.
+func (_u *PaymentOrderUpdateOne) SetQrCodeImg(v string) *PaymentOrderUpdateOne {
+ _u.mutation.SetQrCodeImg(v)
+ return _u
+}
+
+// SetNillableQrCodeImg sets the "qr_code_img" field if the given value is not nil.
+func (_u *PaymentOrderUpdateOne) SetNillableQrCodeImg(v *string) *PaymentOrderUpdateOne {
+ if v != nil {
+ _u.SetQrCodeImg(*v)
+ }
+ return _u
+}
+
+// ClearQrCodeImg clears the value of the "qr_code_img" field.
+func (_u *PaymentOrderUpdateOne) ClearQrCodeImg() *PaymentOrderUpdateOne {
+ _u.mutation.ClearQrCodeImg()
+ return _u
+}
+
+// SetOrderType sets the "order_type" field.
+func (_u *PaymentOrderUpdateOne) SetOrderType(v string) *PaymentOrderUpdateOne {
+ _u.mutation.SetOrderType(v)
+ return _u
+}
+
+// SetNillableOrderType sets the "order_type" field if the given value is not nil.
+func (_u *PaymentOrderUpdateOne) SetNillableOrderType(v *string) *PaymentOrderUpdateOne {
+ if v != nil {
+ _u.SetOrderType(*v)
+ }
+ return _u
+}
+
+// SetPlanID sets the "plan_id" field.
+func (_u *PaymentOrderUpdateOne) SetPlanID(v int64) *PaymentOrderUpdateOne {
+ _u.mutation.ResetPlanID()
+ _u.mutation.SetPlanID(v)
+ return _u
+}
+
+// SetNillablePlanID sets the "plan_id" field if the given value is not nil.
+func (_u *PaymentOrderUpdateOne) SetNillablePlanID(v *int64) *PaymentOrderUpdateOne {
+ if v != nil {
+ _u.SetPlanID(*v)
+ }
+ return _u
+}
+
+// AddPlanID adds value to the "plan_id" field.
+func (_u *PaymentOrderUpdateOne) AddPlanID(v int64) *PaymentOrderUpdateOne {
+ _u.mutation.AddPlanID(v)
+ return _u
+}
+
+// ClearPlanID clears the value of the "plan_id" field.
+func (_u *PaymentOrderUpdateOne) ClearPlanID() *PaymentOrderUpdateOne {
+ _u.mutation.ClearPlanID()
+ return _u
+}
+
+// SetSubscriptionGroupID sets the "subscription_group_id" field.
+func (_u *PaymentOrderUpdateOne) SetSubscriptionGroupID(v int64) *PaymentOrderUpdateOne {
+ _u.mutation.ResetSubscriptionGroupID()
+ _u.mutation.SetSubscriptionGroupID(v)
+ return _u
+}
+
+// SetNillableSubscriptionGroupID sets the "subscription_group_id" field if the given value is not nil.
+func (_u *PaymentOrderUpdateOne) SetNillableSubscriptionGroupID(v *int64) *PaymentOrderUpdateOne {
+ if v != nil {
+ _u.SetSubscriptionGroupID(*v)
+ }
+ return _u
+}
+
+// AddSubscriptionGroupID adds value to the "subscription_group_id" field.
+func (_u *PaymentOrderUpdateOne) AddSubscriptionGroupID(v int64) *PaymentOrderUpdateOne {
+ _u.mutation.AddSubscriptionGroupID(v)
+ return _u
+}
+
+// ClearSubscriptionGroupID clears the value of the "subscription_group_id" field.
+func (_u *PaymentOrderUpdateOne) ClearSubscriptionGroupID() *PaymentOrderUpdateOne {
+ _u.mutation.ClearSubscriptionGroupID()
+ return _u
+}
+
+// SetSubscriptionDays sets the "subscription_days" field.
+func (_u *PaymentOrderUpdateOne) SetSubscriptionDays(v int) *PaymentOrderUpdateOne {
+ _u.mutation.ResetSubscriptionDays()
+ _u.mutation.SetSubscriptionDays(v)
+ return _u
+}
+
+// SetNillableSubscriptionDays sets the "subscription_days" field if the given value is not nil.
+func (_u *PaymentOrderUpdateOne) SetNillableSubscriptionDays(v *int) *PaymentOrderUpdateOne {
+ if v != nil {
+ _u.SetSubscriptionDays(*v)
+ }
+ return _u
+}
+
+// AddSubscriptionDays adds value to the "subscription_days" field.
+func (_u *PaymentOrderUpdateOne) AddSubscriptionDays(v int) *PaymentOrderUpdateOne {
+ _u.mutation.AddSubscriptionDays(v)
+ return _u
+}
+
+// ClearSubscriptionDays clears the value of the "subscription_days" field.
+func (_u *PaymentOrderUpdateOne) ClearSubscriptionDays() *PaymentOrderUpdateOne {
+ _u.mutation.ClearSubscriptionDays()
+ return _u
+}
+
+// SetProviderInstanceID sets the "provider_instance_id" field.
+func (_u *PaymentOrderUpdateOne) SetProviderInstanceID(v string) *PaymentOrderUpdateOne {
+ _u.mutation.SetProviderInstanceID(v)
+ return _u
+}
+
+// SetNillableProviderInstanceID sets the "provider_instance_id" field if the given value is not nil.
+func (_u *PaymentOrderUpdateOne) SetNillableProviderInstanceID(v *string) *PaymentOrderUpdateOne {
+ if v != nil {
+ _u.SetProviderInstanceID(*v)
+ }
+ return _u
+}
+
+// ClearProviderInstanceID clears the value of the "provider_instance_id" field.
+func (_u *PaymentOrderUpdateOne) ClearProviderInstanceID() *PaymentOrderUpdateOne {
+ _u.mutation.ClearProviderInstanceID()
+ return _u
+}
+
+// SetStatus sets the "status" field.
+func (_u *PaymentOrderUpdateOne) SetStatus(v string) *PaymentOrderUpdateOne {
+ _u.mutation.SetStatus(v)
+ return _u
+}
+
+// SetNillableStatus sets the "status" field if the given value is not nil.
+func (_u *PaymentOrderUpdateOne) SetNillableStatus(v *string) *PaymentOrderUpdateOne {
+ if v != nil {
+ _u.SetStatus(*v)
+ }
+ return _u
+}
+
+// SetRefundAmount sets the "refund_amount" field.
+func (_u *PaymentOrderUpdateOne) SetRefundAmount(v float64) *PaymentOrderUpdateOne {
+ _u.mutation.ResetRefundAmount()
+ _u.mutation.SetRefundAmount(v)
+ return _u
+}
+
+// SetNillableRefundAmount sets the "refund_amount" field if the given value is not nil.
+func (_u *PaymentOrderUpdateOne) SetNillableRefundAmount(v *float64) *PaymentOrderUpdateOne {
+ if v != nil {
+ _u.SetRefundAmount(*v)
+ }
+ return _u
+}
+
+// AddRefundAmount adds value to the "refund_amount" field.
+func (_u *PaymentOrderUpdateOne) AddRefundAmount(v float64) *PaymentOrderUpdateOne {
+ _u.mutation.AddRefundAmount(v)
+ return _u
+}
+
+// SetRefundReason sets the "refund_reason" field.
+func (_u *PaymentOrderUpdateOne) SetRefundReason(v string) *PaymentOrderUpdateOne {
+ _u.mutation.SetRefundReason(v)
+ return _u
+}
+
+// SetNillableRefundReason sets the "refund_reason" field if the given value is not nil.
+func (_u *PaymentOrderUpdateOne) SetNillableRefundReason(v *string) *PaymentOrderUpdateOne {
+ if v != nil {
+ _u.SetRefundReason(*v)
+ }
+ return _u
+}
+
+// ClearRefundReason clears the value of the "refund_reason" field.
+func (_u *PaymentOrderUpdateOne) ClearRefundReason() *PaymentOrderUpdateOne {
+ _u.mutation.ClearRefundReason()
+ return _u
+}
+
+// SetRefundAt sets the "refund_at" field.
+func (_u *PaymentOrderUpdateOne) SetRefundAt(v time.Time) *PaymentOrderUpdateOne {
+ _u.mutation.SetRefundAt(v)
+ return _u
+}
+
+// SetNillableRefundAt sets the "refund_at" field if the given value is not nil.
+func (_u *PaymentOrderUpdateOne) SetNillableRefundAt(v *time.Time) *PaymentOrderUpdateOne {
+ if v != nil {
+ _u.SetRefundAt(*v)
+ }
+ return _u
+}
+
+// ClearRefundAt clears the value of the "refund_at" field.
+func (_u *PaymentOrderUpdateOne) ClearRefundAt() *PaymentOrderUpdateOne {
+ _u.mutation.ClearRefundAt()
+ return _u
+}
+
+// SetForceRefund sets the "force_refund" field.
+func (_u *PaymentOrderUpdateOne) SetForceRefund(v bool) *PaymentOrderUpdateOne {
+ _u.mutation.SetForceRefund(v)
+ return _u
+}
+
+// SetNillableForceRefund sets the "force_refund" field if the given value is not nil.
+func (_u *PaymentOrderUpdateOne) SetNillableForceRefund(v *bool) *PaymentOrderUpdateOne {
+ if v != nil {
+ _u.SetForceRefund(*v)
+ }
+ return _u
+}
+
+// SetRefundRequestedAt sets the "refund_requested_at" field.
+func (_u *PaymentOrderUpdateOne) SetRefundRequestedAt(v time.Time) *PaymentOrderUpdateOne {
+ _u.mutation.SetRefundRequestedAt(v)
+ return _u
+}
+
+// SetNillableRefundRequestedAt sets the "refund_requested_at" field if the given value is not nil.
+func (_u *PaymentOrderUpdateOne) SetNillableRefundRequestedAt(v *time.Time) *PaymentOrderUpdateOne {
+ if v != nil {
+ _u.SetRefundRequestedAt(*v)
+ }
+ return _u
+}
+
+// ClearRefundRequestedAt clears the value of the "refund_requested_at" field.
+func (_u *PaymentOrderUpdateOne) ClearRefundRequestedAt() *PaymentOrderUpdateOne {
+ _u.mutation.ClearRefundRequestedAt()
+ return _u
+}
+
+// SetRefundRequestReason sets the "refund_request_reason" field.
+func (_u *PaymentOrderUpdateOne) SetRefundRequestReason(v string) *PaymentOrderUpdateOne {
+ _u.mutation.SetRefundRequestReason(v)
+ return _u
+}
+
+// SetNillableRefundRequestReason sets the "refund_request_reason" field if the given value is not nil.
+func (_u *PaymentOrderUpdateOne) SetNillableRefundRequestReason(v *string) *PaymentOrderUpdateOne {
+ if v != nil {
+ _u.SetRefundRequestReason(*v)
+ }
+ return _u
+}
+
+// ClearRefundRequestReason clears the value of the "refund_request_reason" field.
+func (_u *PaymentOrderUpdateOne) ClearRefundRequestReason() *PaymentOrderUpdateOne {
+ _u.mutation.ClearRefundRequestReason()
+ return _u
+}
+
+// SetRefundRequestedBy sets the "refund_requested_by" field.
+func (_u *PaymentOrderUpdateOne) SetRefundRequestedBy(v string) *PaymentOrderUpdateOne {
+ _u.mutation.SetRefundRequestedBy(v)
+ return _u
+}
+
+// SetNillableRefundRequestedBy sets the "refund_requested_by" field if the given value is not nil.
+func (_u *PaymentOrderUpdateOne) SetNillableRefundRequestedBy(v *string) *PaymentOrderUpdateOne {
+ if v != nil {
+ _u.SetRefundRequestedBy(*v)
+ }
+ return _u
+}
+
+// ClearRefundRequestedBy clears the value of the "refund_requested_by" field.
+func (_u *PaymentOrderUpdateOne) ClearRefundRequestedBy() *PaymentOrderUpdateOne {
+ _u.mutation.ClearRefundRequestedBy()
+ return _u
+}
+
+// SetExpiresAt sets the "expires_at" field.
+func (_u *PaymentOrderUpdateOne) SetExpiresAt(v time.Time) *PaymentOrderUpdateOne {
+ _u.mutation.SetExpiresAt(v)
+ return _u
+}
+
+// SetNillableExpiresAt sets the "expires_at" field if the given value is not nil.
+func (_u *PaymentOrderUpdateOne) SetNillableExpiresAt(v *time.Time) *PaymentOrderUpdateOne {
+ if v != nil {
+ _u.SetExpiresAt(*v)
+ }
+ return _u
+}
+
+// SetPaidAt sets the "paid_at" field.
+func (_u *PaymentOrderUpdateOne) SetPaidAt(v time.Time) *PaymentOrderUpdateOne {
+ _u.mutation.SetPaidAt(v)
+ return _u
+}
+
+// SetNillablePaidAt sets the "paid_at" field if the given value is not nil.
+func (_u *PaymentOrderUpdateOne) SetNillablePaidAt(v *time.Time) *PaymentOrderUpdateOne {
+ if v != nil {
+ _u.SetPaidAt(*v)
+ }
+ return _u
+}
+
+// ClearPaidAt clears the value of the "paid_at" field.
+func (_u *PaymentOrderUpdateOne) ClearPaidAt() *PaymentOrderUpdateOne {
+ _u.mutation.ClearPaidAt()
+ return _u
+}
+
+// SetCompletedAt sets the "completed_at" field.
+func (_u *PaymentOrderUpdateOne) SetCompletedAt(v time.Time) *PaymentOrderUpdateOne {
+ _u.mutation.SetCompletedAt(v)
+ return _u
+}
+
+// SetNillableCompletedAt sets the "completed_at" field if the given value is not nil.
+func (_u *PaymentOrderUpdateOne) SetNillableCompletedAt(v *time.Time) *PaymentOrderUpdateOne {
+ if v != nil {
+ _u.SetCompletedAt(*v)
+ }
+ return _u
+}
+
+// ClearCompletedAt clears the value of the "completed_at" field.
+func (_u *PaymentOrderUpdateOne) ClearCompletedAt() *PaymentOrderUpdateOne {
+ _u.mutation.ClearCompletedAt()
+ return _u
+}
+
+// SetFailedAt sets the "failed_at" field.
+func (_u *PaymentOrderUpdateOne) SetFailedAt(v time.Time) *PaymentOrderUpdateOne {
+ _u.mutation.SetFailedAt(v)
+ return _u
+}
+
+// SetNillableFailedAt sets the "failed_at" field if the given value is not nil.
+func (_u *PaymentOrderUpdateOne) SetNillableFailedAt(v *time.Time) *PaymentOrderUpdateOne {
+ if v != nil {
+ _u.SetFailedAt(*v)
+ }
+ return _u
+}
+
+// ClearFailedAt clears the value of the "failed_at" field.
+func (_u *PaymentOrderUpdateOne) ClearFailedAt() *PaymentOrderUpdateOne {
+ _u.mutation.ClearFailedAt()
+ return _u
+}
+
+// SetFailedReason sets the "failed_reason" field.
+func (_u *PaymentOrderUpdateOne) SetFailedReason(v string) *PaymentOrderUpdateOne {
+ _u.mutation.SetFailedReason(v)
+ return _u
+}
+
+// SetNillableFailedReason sets the "failed_reason" field if the given value is not nil.
+func (_u *PaymentOrderUpdateOne) SetNillableFailedReason(v *string) *PaymentOrderUpdateOne {
+ if v != nil {
+ _u.SetFailedReason(*v)
+ }
+ return _u
+}
+
+// ClearFailedReason clears the value of the "failed_reason" field.
+func (_u *PaymentOrderUpdateOne) ClearFailedReason() *PaymentOrderUpdateOne {
+ _u.mutation.ClearFailedReason()
+ return _u
+}
+
+// SetClientIP sets the "client_ip" field.
+func (_u *PaymentOrderUpdateOne) SetClientIP(v string) *PaymentOrderUpdateOne {
+ _u.mutation.SetClientIP(v)
+ return _u
+}
+
+// SetNillableClientIP sets the "client_ip" field if the given value is not nil.
+func (_u *PaymentOrderUpdateOne) SetNillableClientIP(v *string) *PaymentOrderUpdateOne {
+ if v != nil {
+ _u.SetClientIP(*v)
+ }
+ return _u
+}
+
+// SetSrcHost sets the "src_host" field.
+func (_u *PaymentOrderUpdateOne) SetSrcHost(v string) *PaymentOrderUpdateOne {
+ _u.mutation.SetSrcHost(v)
+ return _u
+}
+
+// SetNillableSrcHost sets the "src_host" field if the given value is not nil.
+func (_u *PaymentOrderUpdateOne) SetNillableSrcHost(v *string) *PaymentOrderUpdateOne {
+ if v != nil {
+ _u.SetSrcHost(*v)
+ }
+ return _u
+}
+
+// SetSrcURL sets the "src_url" field.
+func (_u *PaymentOrderUpdateOne) SetSrcURL(v string) *PaymentOrderUpdateOne {
+ _u.mutation.SetSrcURL(v)
+ return _u
+}
+
+// SetNillableSrcURL sets the "src_url" field if the given value is not nil.
+func (_u *PaymentOrderUpdateOne) SetNillableSrcURL(v *string) *PaymentOrderUpdateOne {
+ if v != nil {
+ _u.SetSrcURL(*v)
+ }
+ return _u
+}
+
+// ClearSrcURL clears the value of the "src_url" field.
+func (_u *PaymentOrderUpdateOne) ClearSrcURL() *PaymentOrderUpdateOne {
+ _u.mutation.ClearSrcURL()
+ return _u
+}
+
+// SetUpdatedAt sets the "updated_at" field.
+func (_u *PaymentOrderUpdateOne) SetUpdatedAt(v time.Time) *PaymentOrderUpdateOne {
+ _u.mutation.SetUpdatedAt(v)
+ return _u
+}
+
+// SetUser sets the "user" edge to the User entity.
+func (_u *PaymentOrderUpdateOne) SetUser(v *User) *PaymentOrderUpdateOne {
+ return _u.SetUserID(v.ID)
+}
+
+// Mutation returns the PaymentOrderMutation object of the builder.
+func (_u *PaymentOrderUpdateOne) Mutation() *PaymentOrderMutation {
+ return _u.mutation
+}
+
+// ClearUser clears the "user" edge to the User entity.
+func (_u *PaymentOrderUpdateOne) ClearUser() *PaymentOrderUpdateOne {
+ _u.mutation.ClearUser()
+ return _u
+}
+
+// Where appends a list predicates to the PaymentOrderUpdate builder.
+func (_u *PaymentOrderUpdateOne) Where(ps ...predicate.PaymentOrder) *PaymentOrderUpdateOne {
+ _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 *PaymentOrderUpdateOne) Select(field string, fields ...string) *PaymentOrderUpdateOne {
+ _u.fields = append([]string{field}, fields...)
+ return _u
+}
+
+// Save executes the query and returns the updated PaymentOrder entity.
+func (_u *PaymentOrderUpdateOne) Save(ctx context.Context) (*PaymentOrder, error) {
+ _u.defaults()
+ return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
+}
+
+// SaveX is like Save, but panics if an error occurs.
+func (_u *PaymentOrderUpdateOne) SaveX(ctx context.Context) *PaymentOrder {
+ node, err := _u.Save(ctx)
+ if err != nil {
+ panic(err)
+ }
+ return node
+}
+
+// Exec executes the query on the entity.
+func (_u *PaymentOrderUpdateOne) Exec(ctx context.Context) error {
+ _, err := _u.Save(ctx)
+ return err
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (_u *PaymentOrderUpdateOne) 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 *PaymentOrderUpdateOne) defaults() {
+ if _, ok := _u.mutation.UpdatedAt(); !ok {
+ v := paymentorder.UpdateDefaultUpdatedAt()
+ _u.mutation.SetUpdatedAt(v)
+ }
+}
+
+// check runs all checks and user-defined validators on the builder.
+func (_u *PaymentOrderUpdateOne) check() error {
+ if v, ok := _u.mutation.UserEmail(); ok {
+ if err := paymentorder.UserEmailValidator(v); err != nil {
+ return &ValidationError{Name: "user_email", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.user_email": %w`, err)}
+ }
+ }
+ if v, ok := _u.mutation.UserName(); ok {
+ if err := paymentorder.UserNameValidator(v); err != nil {
+ return &ValidationError{Name: "user_name", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.user_name": %w`, err)}
+ }
+ }
+ if v, ok := _u.mutation.RechargeCode(); ok {
+ if err := paymentorder.RechargeCodeValidator(v); err != nil {
+ return &ValidationError{Name: "recharge_code", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.recharge_code": %w`, err)}
+ }
+ }
+ if v, ok := _u.mutation.OutTradeNo(); ok {
+ if err := paymentorder.OutTradeNoValidator(v); err != nil {
+ return &ValidationError{Name: "out_trade_no", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.out_trade_no": %w`, err)}
+ }
+ }
+ if v, ok := _u.mutation.PaymentType(); ok {
+ if err := paymentorder.PaymentTypeValidator(v); err != nil {
+ return &ValidationError{Name: "payment_type", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.payment_type": %w`, err)}
+ }
+ }
+ if v, ok := _u.mutation.PaymentTradeNo(); ok {
+ if err := paymentorder.PaymentTradeNoValidator(v); err != nil {
+ return &ValidationError{Name: "payment_trade_no", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.payment_trade_no": %w`, err)}
+ }
+ }
+ if v, ok := _u.mutation.OrderType(); ok {
+ if err := paymentorder.OrderTypeValidator(v); err != nil {
+ return &ValidationError{Name: "order_type", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.order_type": %w`, err)}
+ }
+ }
+ if v, ok := _u.mutation.ProviderInstanceID(); ok {
+ if err := paymentorder.ProviderInstanceIDValidator(v); err != nil {
+ return &ValidationError{Name: "provider_instance_id", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.provider_instance_id": %w`, err)}
+ }
+ }
+ if v, ok := _u.mutation.Status(); ok {
+ if err := paymentorder.StatusValidator(v); err != nil {
+ return &ValidationError{Name: "status", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.status": %w`, err)}
+ }
+ }
+ if v, ok := _u.mutation.RefundRequestedBy(); ok {
+ if err := paymentorder.RefundRequestedByValidator(v); err != nil {
+ return &ValidationError{Name: "refund_requested_by", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.refund_requested_by": %w`, err)}
+ }
+ }
+ if v, ok := _u.mutation.ClientIP(); ok {
+ if err := paymentorder.ClientIPValidator(v); err != nil {
+ return &ValidationError{Name: "client_ip", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.client_ip": %w`, err)}
+ }
+ }
+ if v, ok := _u.mutation.SrcHost(); ok {
+ if err := paymentorder.SrcHostValidator(v); err != nil {
+ return &ValidationError{Name: "src_host", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.src_host": %w`, err)}
+ }
+ }
+ if _u.mutation.UserCleared() && len(_u.mutation.UserIDs()) > 0 {
+ return errors.New(`ent: clearing a required unique edge "PaymentOrder.user"`)
+ }
+ return nil
+}
+
+func (_u *PaymentOrderUpdateOne) sqlSave(ctx context.Context) (_node *PaymentOrder, err error) {
+ if err := _u.check(); err != nil {
+ return _node, err
+ }
+ _spec := sqlgraph.NewUpdateSpec(paymentorder.Table, paymentorder.Columns, sqlgraph.NewFieldSpec(paymentorder.FieldID, field.TypeInt64))
+ id, ok := _u.mutation.ID()
+ if !ok {
+ return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "PaymentOrder.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, paymentorder.FieldID)
+ for _, f := range fields {
+ if !paymentorder.ValidColumn(f) {
+ return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
+ }
+ if f != paymentorder.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.UserEmail(); ok {
+ _spec.SetField(paymentorder.FieldUserEmail, field.TypeString, value)
+ }
+ if value, ok := _u.mutation.UserName(); ok {
+ _spec.SetField(paymentorder.FieldUserName, field.TypeString, value)
+ }
+ if value, ok := _u.mutation.UserNotes(); ok {
+ _spec.SetField(paymentorder.FieldUserNotes, field.TypeString, value)
+ }
+ if _u.mutation.UserNotesCleared() {
+ _spec.ClearField(paymentorder.FieldUserNotes, field.TypeString)
+ }
+ if value, ok := _u.mutation.Amount(); ok {
+ _spec.SetField(paymentorder.FieldAmount, field.TypeFloat64, value)
+ }
+ if value, ok := _u.mutation.AddedAmount(); ok {
+ _spec.AddField(paymentorder.FieldAmount, field.TypeFloat64, value)
+ }
+ if value, ok := _u.mutation.PayAmount(); ok {
+ _spec.SetField(paymentorder.FieldPayAmount, field.TypeFloat64, value)
+ }
+ if value, ok := _u.mutation.AddedPayAmount(); ok {
+ _spec.AddField(paymentorder.FieldPayAmount, field.TypeFloat64, value)
+ }
+ if value, ok := _u.mutation.FeeRate(); ok {
+ _spec.SetField(paymentorder.FieldFeeRate, field.TypeFloat64, value)
+ }
+ if value, ok := _u.mutation.AddedFeeRate(); ok {
+ _spec.AddField(paymentorder.FieldFeeRate, field.TypeFloat64, value)
+ }
+ if value, ok := _u.mutation.RechargeCode(); ok {
+ _spec.SetField(paymentorder.FieldRechargeCode, field.TypeString, value)
+ }
+ if value, ok := _u.mutation.OutTradeNo(); ok {
+ _spec.SetField(paymentorder.FieldOutTradeNo, field.TypeString, value)
+ }
+ if value, ok := _u.mutation.PaymentType(); ok {
+ _spec.SetField(paymentorder.FieldPaymentType, field.TypeString, value)
+ }
+ if value, ok := _u.mutation.PaymentTradeNo(); ok {
+ _spec.SetField(paymentorder.FieldPaymentTradeNo, field.TypeString, value)
+ }
+ if value, ok := _u.mutation.PayURL(); ok {
+ _spec.SetField(paymentorder.FieldPayURL, field.TypeString, value)
+ }
+ if _u.mutation.PayURLCleared() {
+ _spec.ClearField(paymentorder.FieldPayURL, field.TypeString)
+ }
+ if value, ok := _u.mutation.QrCode(); ok {
+ _spec.SetField(paymentorder.FieldQrCode, field.TypeString, value)
+ }
+ if _u.mutation.QrCodeCleared() {
+ _spec.ClearField(paymentorder.FieldQrCode, field.TypeString)
+ }
+ if value, ok := _u.mutation.QrCodeImg(); ok {
+ _spec.SetField(paymentorder.FieldQrCodeImg, field.TypeString, value)
+ }
+ if _u.mutation.QrCodeImgCleared() {
+ _spec.ClearField(paymentorder.FieldQrCodeImg, field.TypeString)
+ }
+ if value, ok := _u.mutation.OrderType(); ok {
+ _spec.SetField(paymentorder.FieldOrderType, field.TypeString, value)
+ }
+ if value, ok := _u.mutation.PlanID(); ok {
+ _spec.SetField(paymentorder.FieldPlanID, field.TypeInt64, value)
+ }
+ if value, ok := _u.mutation.AddedPlanID(); ok {
+ _spec.AddField(paymentorder.FieldPlanID, field.TypeInt64, value)
+ }
+ if _u.mutation.PlanIDCleared() {
+ _spec.ClearField(paymentorder.FieldPlanID, field.TypeInt64)
+ }
+ if value, ok := _u.mutation.SubscriptionGroupID(); ok {
+ _spec.SetField(paymentorder.FieldSubscriptionGroupID, field.TypeInt64, value)
+ }
+ if value, ok := _u.mutation.AddedSubscriptionGroupID(); ok {
+ _spec.AddField(paymentorder.FieldSubscriptionGroupID, field.TypeInt64, value)
+ }
+ if _u.mutation.SubscriptionGroupIDCleared() {
+ _spec.ClearField(paymentorder.FieldSubscriptionGroupID, field.TypeInt64)
+ }
+ if value, ok := _u.mutation.SubscriptionDays(); ok {
+ _spec.SetField(paymentorder.FieldSubscriptionDays, field.TypeInt, value)
+ }
+ if value, ok := _u.mutation.AddedSubscriptionDays(); ok {
+ _spec.AddField(paymentorder.FieldSubscriptionDays, field.TypeInt, value)
+ }
+ if _u.mutation.SubscriptionDaysCleared() {
+ _spec.ClearField(paymentorder.FieldSubscriptionDays, field.TypeInt)
+ }
+ if value, ok := _u.mutation.ProviderInstanceID(); ok {
+ _spec.SetField(paymentorder.FieldProviderInstanceID, field.TypeString, value)
+ }
+ if _u.mutation.ProviderInstanceIDCleared() {
+ _spec.ClearField(paymentorder.FieldProviderInstanceID, field.TypeString)
+ }
+ if value, ok := _u.mutation.Status(); ok {
+ _spec.SetField(paymentorder.FieldStatus, field.TypeString, value)
+ }
+ if value, ok := _u.mutation.RefundAmount(); ok {
+ _spec.SetField(paymentorder.FieldRefundAmount, field.TypeFloat64, value)
+ }
+ if value, ok := _u.mutation.AddedRefundAmount(); ok {
+ _spec.AddField(paymentorder.FieldRefundAmount, field.TypeFloat64, value)
+ }
+ if value, ok := _u.mutation.RefundReason(); ok {
+ _spec.SetField(paymentorder.FieldRefundReason, field.TypeString, value)
+ }
+ if _u.mutation.RefundReasonCleared() {
+ _spec.ClearField(paymentorder.FieldRefundReason, field.TypeString)
+ }
+ if value, ok := _u.mutation.RefundAt(); ok {
+ _spec.SetField(paymentorder.FieldRefundAt, field.TypeTime, value)
+ }
+ if _u.mutation.RefundAtCleared() {
+ _spec.ClearField(paymentorder.FieldRefundAt, field.TypeTime)
+ }
+ if value, ok := _u.mutation.ForceRefund(); ok {
+ _spec.SetField(paymentorder.FieldForceRefund, field.TypeBool, value)
+ }
+ if value, ok := _u.mutation.RefundRequestedAt(); ok {
+ _spec.SetField(paymentorder.FieldRefundRequestedAt, field.TypeTime, value)
+ }
+ if _u.mutation.RefundRequestedAtCleared() {
+ _spec.ClearField(paymentorder.FieldRefundRequestedAt, field.TypeTime)
+ }
+ if value, ok := _u.mutation.RefundRequestReason(); ok {
+ _spec.SetField(paymentorder.FieldRefundRequestReason, field.TypeString, value)
+ }
+ if _u.mutation.RefundRequestReasonCleared() {
+ _spec.ClearField(paymentorder.FieldRefundRequestReason, field.TypeString)
+ }
+ if value, ok := _u.mutation.RefundRequestedBy(); ok {
+ _spec.SetField(paymentorder.FieldRefundRequestedBy, field.TypeString, value)
+ }
+ if _u.mutation.RefundRequestedByCleared() {
+ _spec.ClearField(paymentorder.FieldRefundRequestedBy, field.TypeString)
+ }
+ if value, ok := _u.mutation.ExpiresAt(); ok {
+ _spec.SetField(paymentorder.FieldExpiresAt, field.TypeTime, value)
+ }
+ if value, ok := _u.mutation.PaidAt(); ok {
+ _spec.SetField(paymentorder.FieldPaidAt, field.TypeTime, value)
+ }
+ if _u.mutation.PaidAtCleared() {
+ _spec.ClearField(paymentorder.FieldPaidAt, field.TypeTime)
+ }
+ if value, ok := _u.mutation.CompletedAt(); ok {
+ _spec.SetField(paymentorder.FieldCompletedAt, field.TypeTime, value)
+ }
+ if _u.mutation.CompletedAtCleared() {
+ _spec.ClearField(paymentorder.FieldCompletedAt, field.TypeTime)
+ }
+ if value, ok := _u.mutation.FailedAt(); ok {
+ _spec.SetField(paymentorder.FieldFailedAt, field.TypeTime, value)
+ }
+ if _u.mutation.FailedAtCleared() {
+ _spec.ClearField(paymentorder.FieldFailedAt, field.TypeTime)
+ }
+ if value, ok := _u.mutation.FailedReason(); ok {
+ _spec.SetField(paymentorder.FieldFailedReason, field.TypeString, value)
+ }
+ if _u.mutation.FailedReasonCleared() {
+ _spec.ClearField(paymentorder.FieldFailedReason, field.TypeString)
+ }
+ if value, ok := _u.mutation.ClientIP(); ok {
+ _spec.SetField(paymentorder.FieldClientIP, field.TypeString, value)
+ }
+ if value, ok := _u.mutation.SrcHost(); ok {
+ _spec.SetField(paymentorder.FieldSrcHost, field.TypeString, value)
+ }
+ if value, ok := _u.mutation.SrcURL(); ok {
+ _spec.SetField(paymentorder.FieldSrcURL, field.TypeString, value)
+ }
+ if _u.mutation.SrcURLCleared() {
+ _spec.ClearField(paymentorder.FieldSrcURL, field.TypeString)
+ }
+ if value, ok := _u.mutation.UpdatedAt(); ok {
+ _spec.SetField(paymentorder.FieldUpdatedAt, field.TypeTime, value)
+ }
+ if _u.mutation.UserCleared() {
+ edge := &sqlgraph.EdgeSpec{
+ Rel: sqlgraph.M2O,
+ Inverse: true,
+ Table: paymentorder.UserTable,
+ Columns: []string{paymentorder.UserColumn},
+ Bidi: false,
+ Target: &sqlgraph.EdgeTarget{
+ IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt64),
+ },
+ }
+ _spec.Edges.Clear = append(_spec.Edges.Clear, edge)
+ }
+ if nodes := _u.mutation.UserIDs(); len(nodes) > 0 {
+ edge := &sqlgraph.EdgeSpec{
+ Rel: sqlgraph.M2O,
+ Inverse: true,
+ Table: paymentorder.UserTable,
+ Columns: []string{paymentorder.UserColumn},
+ Bidi: false,
+ Target: &sqlgraph.EdgeTarget{
+ IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt64),
+ },
+ }
+ for _, k := range nodes {
+ edge.Target.Nodes = append(edge.Target.Nodes, k)
+ }
+ _spec.Edges.Add = append(_spec.Edges.Add, edge)
+ }
+ _node = &PaymentOrder{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{paymentorder.Label}
+ } else if sqlgraph.IsConstraintError(err) {
+ err = &ConstraintError{msg: err.Error(), wrap: err}
+ }
+ return nil, err
+ }
+ _u.mutation.done = true
+ return _node, nil
+}
diff --git a/backend/ent/paymentproviderinstance.go b/backend/ent/paymentproviderinstance.go
new file mode 100644
index 0000000000000000000000000000000000000000..087cb13a2006507aecebe910e68d2bade1e3ca3e
--- /dev/null
+++ b/backend/ent/paymentproviderinstance.go
@@ -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
diff --git a/backend/ent/paymentproviderinstance/paymentproviderinstance.go b/backend/ent/paymentproviderinstance/paymentproviderinstance.go
new file mode 100644
index 0000000000000000000000000000000000000000..c430fef6c3b42ed54782065763ac693561f3c2ef
--- /dev/null
+++ b/backend/ent/paymentproviderinstance/paymentproviderinstance.go
@@ -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()
+}
diff --git a/backend/ent/paymentproviderinstance/where.go b/backend/ent/paymentproviderinstance/where.go
new file mode 100644
index 0000000000000000000000000000000000000000..7b99517f3ad94006f15e800f1b7729914ddee7ab
--- /dev/null
+++ b/backend/ent/paymentproviderinstance/where.go
@@ -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))
+}
diff --git a/backend/ent/paymentproviderinstance_create.go b/backend/ent/paymentproviderinstance_create.go
new file mode 100644
index 0000000000000000000000000000000000000000..20b16ddd42e6aaee245920b6dc4bc44858fc66a4
--- /dev/null
+++ b/backend/ent/paymentproviderinstance_create.go
@@ -0,0 +1,1111 @@
+// 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"
+)
+
+// PaymentProviderInstanceCreate is the builder for creating a PaymentProviderInstance entity.
+type PaymentProviderInstanceCreate struct {
+ config
+ mutation *PaymentProviderInstanceMutation
+ hooks []Hook
+ conflict []sql.ConflictOption
+}
+
+// SetProviderKey sets the "provider_key" field.
+func (_c *PaymentProviderInstanceCreate) SetProviderKey(v string) *PaymentProviderInstanceCreate {
+ _c.mutation.SetProviderKey(v)
+ return _c
+}
+
+// SetName sets the "name" field.
+func (_c *PaymentProviderInstanceCreate) SetName(v string) *PaymentProviderInstanceCreate {
+ _c.mutation.SetName(v)
+ return _c
+}
+
+// SetNillableName sets the "name" field if the given value is not nil.
+func (_c *PaymentProviderInstanceCreate) SetNillableName(v *string) *PaymentProviderInstanceCreate {
+ if v != nil {
+ _c.SetName(*v)
+ }
+ return _c
+}
+
+// SetConfig sets the "config" field.
+func (_c *PaymentProviderInstanceCreate) SetConfig(v string) *PaymentProviderInstanceCreate {
+ _c.mutation.SetConfig(v)
+ return _c
+}
+
+// SetSupportedTypes sets the "supported_types" field.
+func (_c *PaymentProviderInstanceCreate) SetSupportedTypes(v string) *PaymentProviderInstanceCreate {
+ _c.mutation.SetSupportedTypes(v)
+ return _c
+}
+
+// SetNillableSupportedTypes sets the "supported_types" field if the given value is not nil.
+func (_c *PaymentProviderInstanceCreate) SetNillableSupportedTypes(v *string) *PaymentProviderInstanceCreate {
+ if v != nil {
+ _c.SetSupportedTypes(*v)
+ }
+ return _c
+}
+
+// SetEnabled sets the "enabled" field.
+func (_c *PaymentProviderInstanceCreate) SetEnabled(v bool) *PaymentProviderInstanceCreate {
+ _c.mutation.SetEnabled(v)
+ return _c
+}
+
+// SetNillableEnabled sets the "enabled" field if the given value is not nil.
+func (_c *PaymentProviderInstanceCreate) SetNillableEnabled(v *bool) *PaymentProviderInstanceCreate {
+ if v != nil {
+ _c.SetEnabled(*v)
+ }
+ return _c
+}
+
+// SetPaymentMode sets the "payment_mode" field.
+func (_c *PaymentProviderInstanceCreate) SetPaymentMode(v string) *PaymentProviderInstanceCreate {
+ _c.mutation.SetPaymentMode(v)
+ return _c
+}
+
+// SetNillablePaymentMode sets the "payment_mode" field if the given value is not nil.
+func (_c *PaymentProviderInstanceCreate) SetNillablePaymentMode(v *string) *PaymentProviderInstanceCreate {
+ if v != nil {
+ _c.SetPaymentMode(*v)
+ }
+ return _c
+}
+
+// SetSortOrder sets the "sort_order" field.
+func (_c *PaymentProviderInstanceCreate) SetSortOrder(v int) *PaymentProviderInstanceCreate {
+ _c.mutation.SetSortOrder(v)
+ return _c
+}
+
+// SetNillableSortOrder sets the "sort_order" field if the given value is not nil.
+func (_c *PaymentProviderInstanceCreate) SetNillableSortOrder(v *int) *PaymentProviderInstanceCreate {
+ if v != nil {
+ _c.SetSortOrder(*v)
+ }
+ return _c
+}
+
+// SetLimits sets the "limits" field.
+func (_c *PaymentProviderInstanceCreate) SetLimits(v string) *PaymentProviderInstanceCreate {
+ _c.mutation.SetLimits(v)
+ return _c
+}
+
+// SetNillableLimits sets the "limits" field if the given value is not nil.
+func (_c *PaymentProviderInstanceCreate) SetNillableLimits(v *string) *PaymentProviderInstanceCreate {
+ if v != nil {
+ _c.SetLimits(*v)
+ }
+ return _c
+}
+
+// SetRefundEnabled sets the "refund_enabled" field.
+func (_c *PaymentProviderInstanceCreate) SetRefundEnabled(v bool) *PaymentProviderInstanceCreate {
+ _c.mutation.SetRefundEnabled(v)
+ return _c
+}
+
+// SetNillableRefundEnabled sets the "refund_enabled" field if the given value is not nil.
+func (_c *PaymentProviderInstanceCreate) SetNillableRefundEnabled(v *bool) *PaymentProviderInstanceCreate {
+ if v != nil {
+ _c.SetRefundEnabled(*v)
+ }
+ return _c
+}
+
+// SetCreatedAt sets the "created_at" field.
+func (_c *PaymentProviderInstanceCreate) SetCreatedAt(v time.Time) *PaymentProviderInstanceCreate {
+ _c.mutation.SetCreatedAt(v)
+ return _c
+}
+
+// SetNillableCreatedAt sets the "created_at" field if the given value is not nil.
+func (_c *PaymentProviderInstanceCreate) SetNillableCreatedAt(v *time.Time) *PaymentProviderInstanceCreate {
+ if v != nil {
+ _c.SetCreatedAt(*v)
+ }
+ return _c
+}
+
+// SetUpdatedAt sets the "updated_at" field.
+func (_c *PaymentProviderInstanceCreate) SetUpdatedAt(v time.Time) *PaymentProviderInstanceCreate {
+ _c.mutation.SetUpdatedAt(v)
+ return _c
+}
+
+// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil.
+func (_c *PaymentProviderInstanceCreate) SetNillableUpdatedAt(v *time.Time) *PaymentProviderInstanceCreate {
+ if v != nil {
+ _c.SetUpdatedAt(*v)
+ }
+ return _c
+}
+
+// Mutation returns the PaymentProviderInstanceMutation object of the builder.
+func (_c *PaymentProviderInstanceCreate) Mutation() *PaymentProviderInstanceMutation {
+ return _c.mutation
+}
+
+// Save creates the PaymentProviderInstance in the database.
+func (_c *PaymentProviderInstanceCreate) Save(ctx context.Context) (*PaymentProviderInstance, error) {
+ _c.defaults()
+ return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks)
+}
+
+// SaveX calls Save and panics if Save returns an error.
+func (_c *PaymentProviderInstanceCreate) SaveX(ctx context.Context) *PaymentProviderInstance {
+ v, err := _c.Save(ctx)
+ if err != nil {
+ panic(err)
+ }
+ return v
+}
+
+// Exec executes the query.
+func (_c *PaymentProviderInstanceCreate) Exec(ctx context.Context) error {
+ _, err := _c.Save(ctx)
+ return err
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (_c *PaymentProviderInstanceCreate) 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 *PaymentProviderInstanceCreate) defaults() {
+ if _, ok := _c.mutation.Name(); !ok {
+ v := paymentproviderinstance.DefaultName
+ _c.mutation.SetName(v)
+ }
+ if _, ok := _c.mutation.SupportedTypes(); !ok {
+ v := paymentproviderinstance.DefaultSupportedTypes
+ _c.mutation.SetSupportedTypes(v)
+ }
+ if _, ok := _c.mutation.Enabled(); !ok {
+ v := paymentproviderinstance.DefaultEnabled
+ _c.mutation.SetEnabled(v)
+ }
+ if _, ok := _c.mutation.PaymentMode(); !ok {
+ v := paymentproviderinstance.DefaultPaymentMode
+ _c.mutation.SetPaymentMode(v)
+ }
+ if _, ok := _c.mutation.SortOrder(); !ok {
+ v := paymentproviderinstance.DefaultSortOrder
+ _c.mutation.SetSortOrder(v)
+ }
+ if _, ok := _c.mutation.Limits(); !ok {
+ v := paymentproviderinstance.DefaultLimits
+ _c.mutation.SetLimits(v)
+ }
+ if _, ok := _c.mutation.RefundEnabled(); !ok {
+ v := paymentproviderinstance.DefaultRefundEnabled
+ _c.mutation.SetRefundEnabled(v)
+ }
+ if _, ok := _c.mutation.CreatedAt(); !ok {
+ v := paymentproviderinstance.DefaultCreatedAt()
+ _c.mutation.SetCreatedAt(v)
+ }
+ if _, ok := _c.mutation.UpdatedAt(); !ok {
+ v := paymentproviderinstance.DefaultUpdatedAt()
+ _c.mutation.SetUpdatedAt(v)
+ }
+}
+
+// check runs all checks and user-defined validators on the builder.
+func (_c *PaymentProviderInstanceCreate) check() error {
+ if _, ok := _c.mutation.ProviderKey(); !ok {
+ return &ValidationError{Name: "provider_key", err: errors.New(`ent: missing required field "PaymentProviderInstance.provider_key"`)}
+ }
+ if v, ok := _c.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 _, ok := _c.mutation.Name(); !ok {
+ return &ValidationError{Name: "name", err: errors.New(`ent: missing required field "PaymentProviderInstance.name"`)}
+ }
+ if v, ok := _c.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 _, ok := _c.mutation.Config(); !ok {
+ return &ValidationError{Name: "config", err: errors.New(`ent: missing required field "PaymentProviderInstance.config"`)}
+ }
+ if _, ok := _c.mutation.SupportedTypes(); !ok {
+ return &ValidationError{Name: "supported_types", err: errors.New(`ent: missing required field "PaymentProviderInstance.supported_types"`)}
+ }
+ if v, ok := _c.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 _, ok := _c.mutation.Enabled(); !ok {
+ return &ValidationError{Name: "enabled", err: errors.New(`ent: missing required field "PaymentProviderInstance.enabled"`)}
+ }
+ if _, ok := _c.mutation.PaymentMode(); !ok {
+ return &ValidationError{Name: "payment_mode", err: errors.New(`ent: missing required field "PaymentProviderInstance.payment_mode"`)}
+ }
+ if v, ok := _c.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)}
+ }
+ }
+ if _, ok := _c.mutation.SortOrder(); !ok {
+ return &ValidationError{Name: "sort_order", err: errors.New(`ent: missing required field "PaymentProviderInstance.sort_order"`)}
+ }
+ if _, ok := _c.mutation.Limits(); !ok {
+ return &ValidationError{Name: "limits", err: errors.New(`ent: missing required field "PaymentProviderInstance.limits"`)}
+ }
+ if _, ok := _c.mutation.RefundEnabled(); !ok {
+ return &ValidationError{Name: "refund_enabled", err: errors.New(`ent: missing required field "PaymentProviderInstance.refund_enabled"`)}
+ }
+ if _, ok := _c.mutation.CreatedAt(); !ok {
+ return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "PaymentProviderInstance.created_at"`)}
+ }
+ if _, ok := _c.mutation.UpdatedAt(); !ok {
+ return &ValidationError{Name: "updated_at", err: errors.New(`ent: missing required field "PaymentProviderInstance.updated_at"`)}
+ }
+ return nil
+}
+
+func (_c *PaymentProviderInstanceCreate) sqlSave(ctx context.Context) (*PaymentProviderInstance, 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 *PaymentProviderInstanceCreate) createSpec() (*PaymentProviderInstance, *sqlgraph.CreateSpec) {
+ var (
+ _node = &PaymentProviderInstance{config: _c.config}
+ _spec = sqlgraph.NewCreateSpec(paymentproviderinstance.Table, sqlgraph.NewFieldSpec(paymentproviderinstance.FieldID, field.TypeInt64))
+ )
+ _spec.OnConflict = _c.conflict
+ if value, ok := _c.mutation.ProviderKey(); ok {
+ _spec.SetField(paymentproviderinstance.FieldProviderKey, field.TypeString, value)
+ _node.ProviderKey = value
+ }
+ if value, ok := _c.mutation.Name(); ok {
+ _spec.SetField(paymentproviderinstance.FieldName, field.TypeString, value)
+ _node.Name = value
+ }
+ if value, ok := _c.mutation.Config(); ok {
+ _spec.SetField(paymentproviderinstance.FieldConfig, field.TypeString, value)
+ _node.Config = value
+ }
+ if value, ok := _c.mutation.SupportedTypes(); ok {
+ _spec.SetField(paymentproviderinstance.FieldSupportedTypes, field.TypeString, value)
+ _node.SupportedTypes = value
+ }
+ if value, ok := _c.mutation.Enabled(); ok {
+ _spec.SetField(paymentproviderinstance.FieldEnabled, field.TypeBool, value)
+ _node.Enabled = value
+ }
+ if value, ok := _c.mutation.PaymentMode(); ok {
+ _spec.SetField(paymentproviderinstance.FieldPaymentMode, field.TypeString, value)
+ _node.PaymentMode = value
+ }
+ if value, ok := _c.mutation.SortOrder(); ok {
+ _spec.SetField(paymentproviderinstance.FieldSortOrder, field.TypeInt, value)
+ _node.SortOrder = value
+ }
+ if value, ok := _c.mutation.Limits(); ok {
+ _spec.SetField(paymentproviderinstance.FieldLimits, field.TypeString, value)
+ _node.Limits = value
+ }
+ if value, ok := _c.mutation.RefundEnabled(); ok {
+ _spec.SetField(paymentproviderinstance.FieldRefundEnabled, field.TypeBool, value)
+ _node.RefundEnabled = value
+ }
+ if value, ok := _c.mutation.CreatedAt(); ok {
+ _spec.SetField(paymentproviderinstance.FieldCreatedAt, field.TypeTime, value)
+ _node.CreatedAt = value
+ }
+ if value, ok := _c.mutation.UpdatedAt(); ok {
+ _spec.SetField(paymentproviderinstance.FieldUpdatedAt, field.TypeTime, value)
+ _node.UpdatedAt = value
+ }
+ return _node, _spec
+}
+
+// OnConflict allows configuring the `ON CONFLICT` / `ON DUPLICATE KEY` clause
+// of the `INSERT` statement. For example:
+//
+// client.PaymentProviderInstance.Create().
+// SetProviderKey(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.PaymentProviderInstanceUpsert) {
+// SetProviderKey(v+v).
+// }).
+// Exec(ctx)
+func (_c *PaymentProviderInstanceCreate) OnConflict(opts ...sql.ConflictOption) *PaymentProviderInstanceUpsertOne {
+ _c.conflict = opts
+ return &PaymentProviderInstanceUpsertOne{
+ create: _c,
+ }
+}
+
+// OnConflictColumns calls `OnConflict` and configures the columns
+// as conflict target. Using this option is equivalent to using:
+//
+// client.PaymentProviderInstance.Create().
+// OnConflict(sql.ConflictColumns(columns...)).
+// Exec(ctx)
+func (_c *PaymentProviderInstanceCreate) OnConflictColumns(columns ...string) *PaymentProviderInstanceUpsertOne {
+ _c.conflict = append(_c.conflict, sql.ConflictColumns(columns...))
+ return &PaymentProviderInstanceUpsertOne{
+ create: _c,
+ }
+}
+
+type (
+ // PaymentProviderInstanceUpsertOne is the builder for "upsert"-ing
+ // one PaymentProviderInstance node.
+ PaymentProviderInstanceUpsertOne struct {
+ create *PaymentProviderInstanceCreate
+ }
+
+ // PaymentProviderInstanceUpsert is the "OnConflict" setter.
+ PaymentProviderInstanceUpsert struct {
+ *sql.UpdateSet
+ }
+)
+
+// SetProviderKey sets the "provider_key" field.
+func (u *PaymentProviderInstanceUpsert) SetProviderKey(v string) *PaymentProviderInstanceUpsert {
+ u.Set(paymentproviderinstance.FieldProviderKey, v)
+ return u
+}
+
+// UpdateProviderKey sets the "provider_key" field to the value that was provided on create.
+func (u *PaymentProviderInstanceUpsert) UpdateProviderKey() *PaymentProviderInstanceUpsert {
+ u.SetExcluded(paymentproviderinstance.FieldProviderKey)
+ return u
+}
+
+// SetName sets the "name" field.
+func (u *PaymentProviderInstanceUpsert) SetName(v string) *PaymentProviderInstanceUpsert {
+ u.Set(paymentproviderinstance.FieldName, v)
+ return u
+}
+
+// UpdateName sets the "name" field to the value that was provided on create.
+func (u *PaymentProviderInstanceUpsert) UpdateName() *PaymentProviderInstanceUpsert {
+ u.SetExcluded(paymentproviderinstance.FieldName)
+ return u
+}
+
+// SetConfig sets the "config" field.
+func (u *PaymentProviderInstanceUpsert) SetConfig(v string) *PaymentProviderInstanceUpsert {
+ u.Set(paymentproviderinstance.FieldConfig, v)
+ return u
+}
+
+// UpdateConfig sets the "config" field to the value that was provided on create.
+func (u *PaymentProviderInstanceUpsert) UpdateConfig() *PaymentProviderInstanceUpsert {
+ u.SetExcluded(paymentproviderinstance.FieldConfig)
+ return u
+}
+
+// SetSupportedTypes sets the "supported_types" field.
+func (u *PaymentProviderInstanceUpsert) SetSupportedTypes(v string) *PaymentProviderInstanceUpsert {
+ u.Set(paymentproviderinstance.FieldSupportedTypes, v)
+ return u
+}
+
+// UpdateSupportedTypes sets the "supported_types" field to the value that was provided on create.
+func (u *PaymentProviderInstanceUpsert) UpdateSupportedTypes() *PaymentProviderInstanceUpsert {
+ u.SetExcluded(paymentproviderinstance.FieldSupportedTypes)
+ return u
+}
+
+// SetEnabled sets the "enabled" field.
+func (u *PaymentProviderInstanceUpsert) SetEnabled(v bool) *PaymentProviderInstanceUpsert {
+ u.Set(paymentproviderinstance.FieldEnabled, v)
+ return u
+}
+
+// UpdateEnabled sets the "enabled" field to the value that was provided on create.
+func (u *PaymentProviderInstanceUpsert) UpdateEnabled() *PaymentProviderInstanceUpsert {
+ u.SetExcluded(paymentproviderinstance.FieldEnabled)
+ return u
+}
+
+// SetPaymentMode sets the "payment_mode" field.
+func (u *PaymentProviderInstanceUpsert) SetPaymentMode(v string) *PaymentProviderInstanceUpsert {
+ u.Set(paymentproviderinstance.FieldPaymentMode, v)
+ return u
+}
+
+// UpdatePaymentMode sets the "payment_mode" field to the value that was provided on create.
+func (u *PaymentProviderInstanceUpsert) UpdatePaymentMode() *PaymentProviderInstanceUpsert {
+ u.SetExcluded(paymentproviderinstance.FieldPaymentMode)
+ return u
+}
+
+// SetSortOrder sets the "sort_order" field.
+func (u *PaymentProviderInstanceUpsert) SetSortOrder(v int) *PaymentProviderInstanceUpsert {
+ u.Set(paymentproviderinstance.FieldSortOrder, v)
+ return u
+}
+
+// UpdateSortOrder sets the "sort_order" field to the value that was provided on create.
+func (u *PaymentProviderInstanceUpsert) UpdateSortOrder() *PaymentProviderInstanceUpsert {
+ u.SetExcluded(paymentproviderinstance.FieldSortOrder)
+ return u
+}
+
+// AddSortOrder adds v to the "sort_order" field.
+func (u *PaymentProviderInstanceUpsert) AddSortOrder(v int) *PaymentProviderInstanceUpsert {
+ u.Add(paymentproviderinstance.FieldSortOrder, v)
+ return u
+}
+
+// SetLimits sets the "limits" field.
+func (u *PaymentProviderInstanceUpsert) SetLimits(v string) *PaymentProviderInstanceUpsert {
+ u.Set(paymentproviderinstance.FieldLimits, v)
+ return u
+}
+
+// UpdateLimits sets the "limits" field to the value that was provided on create.
+func (u *PaymentProviderInstanceUpsert) UpdateLimits() *PaymentProviderInstanceUpsert {
+ u.SetExcluded(paymentproviderinstance.FieldLimits)
+ return u
+}
+
+// SetRefundEnabled sets the "refund_enabled" field.
+func (u *PaymentProviderInstanceUpsert) SetRefundEnabled(v bool) *PaymentProviderInstanceUpsert {
+ u.Set(paymentproviderinstance.FieldRefundEnabled, v)
+ return u
+}
+
+// UpdateRefundEnabled sets the "refund_enabled" field to the value that was provided on create.
+func (u *PaymentProviderInstanceUpsert) UpdateRefundEnabled() *PaymentProviderInstanceUpsert {
+ u.SetExcluded(paymentproviderinstance.FieldRefundEnabled)
+ return u
+}
+
+// SetUpdatedAt sets the "updated_at" field.
+func (u *PaymentProviderInstanceUpsert) SetUpdatedAt(v time.Time) *PaymentProviderInstanceUpsert {
+ u.Set(paymentproviderinstance.FieldUpdatedAt, v)
+ return u
+}
+
+// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create.
+func (u *PaymentProviderInstanceUpsert) UpdateUpdatedAt() *PaymentProviderInstanceUpsert {
+ u.SetExcluded(paymentproviderinstance.FieldUpdatedAt)
+ return u
+}
+
+// UpdateNewValues updates the mutable fields using the new values that were set on create.
+// Using this option is equivalent to using:
+//
+// client.PaymentProviderInstance.Create().
+// OnConflict(
+// sql.ResolveWithNewValues(),
+// ).
+// Exec(ctx)
+func (u *PaymentProviderInstanceUpsertOne) UpdateNewValues() *PaymentProviderInstanceUpsertOne {
+ 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(paymentproviderinstance.FieldCreatedAt)
+ }
+ }))
+ return u
+}
+
+// Ignore sets each column to itself in case of conflict.
+// Using this option is equivalent to using:
+//
+// client.PaymentProviderInstance.Create().
+// OnConflict(sql.ResolveWithIgnore()).
+// Exec(ctx)
+func (u *PaymentProviderInstanceUpsertOne) Ignore() *PaymentProviderInstanceUpsertOne {
+ 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 *PaymentProviderInstanceUpsertOne) DoNothing() *PaymentProviderInstanceUpsertOne {
+ u.create.conflict = append(u.create.conflict, sql.DoNothing())
+ return u
+}
+
+// Update allows overriding fields `UPDATE` values. See the PaymentProviderInstanceCreate.OnConflict
+// documentation for more info.
+func (u *PaymentProviderInstanceUpsertOne) Update(set func(*PaymentProviderInstanceUpsert)) *PaymentProviderInstanceUpsertOne {
+ u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) {
+ set(&PaymentProviderInstanceUpsert{UpdateSet: update})
+ }))
+ return u
+}
+
+// SetProviderKey sets the "provider_key" field.
+func (u *PaymentProviderInstanceUpsertOne) SetProviderKey(v string) *PaymentProviderInstanceUpsertOne {
+ return u.Update(func(s *PaymentProviderInstanceUpsert) {
+ s.SetProviderKey(v)
+ })
+}
+
+// UpdateProviderKey sets the "provider_key" field to the value that was provided on create.
+func (u *PaymentProviderInstanceUpsertOne) UpdateProviderKey() *PaymentProviderInstanceUpsertOne {
+ return u.Update(func(s *PaymentProviderInstanceUpsert) {
+ s.UpdateProviderKey()
+ })
+}
+
+// SetName sets the "name" field.
+func (u *PaymentProviderInstanceUpsertOne) SetName(v string) *PaymentProviderInstanceUpsertOne {
+ return u.Update(func(s *PaymentProviderInstanceUpsert) {
+ s.SetName(v)
+ })
+}
+
+// UpdateName sets the "name" field to the value that was provided on create.
+func (u *PaymentProviderInstanceUpsertOne) UpdateName() *PaymentProviderInstanceUpsertOne {
+ return u.Update(func(s *PaymentProviderInstanceUpsert) {
+ s.UpdateName()
+ })
+}
+
+// SetConfig sets the "config" field.
+func (u *PaymentProviderInstanceUpsertOne) SetConfig(v string) *PaymentProviderInstanceUpsertOne {
+ return u.Update(func(s *PaymentProviderInstanceUpsert) {
+ s.SetConfig(v)
+ })
+}
+
+// UpdateConfig sets the "config" field to the value that was provided on create.
+func (u *PaymentProviderInstanceUpsertOne) UpdateConfig() *PaymentProviderInstanceUpsertOne {
+ return u.Update(func(s *PaymentProviderInstanceUpsert) {
+ s.UpdateConfig()
+ })
+}
+
+// SetSupportedTypes sets the "supported_types" field.
+func (u *PaymentProviderInstanceUpsertOne) SetSupportedTypes(v string) *PaymentProviderInstanceUpsertOne {
+ return u.Update(func(s *PaymentProviderInstanceUpsert) {
+ s.SetSupportedTypes(v)
+ })
+}
+
+// UpdateSupportedTypes sets the "supported_types" field to the value that was provided on create.
+func (u *PaymentProviderInstanceUpsertOne) UpdateSupportedTypes() *PaymentProviderInstanceUpsertOne {
+ return u.Update(func(s *PaymentProviderInstanceUpsert) {
+ s.UpdateSupportedTypes()
+ })
+}
+
+// SetEnabled sets the "enabled" field.
+func (u *PaymentProviderInstanceUpsertOne) SetEnabled(v bool) *PaymentProviderInstanceUpsertOne {
+ return u.Update(func(s *PaymentProviderInstanceUpsert) {
+ s.SetEnabled(v)
+ })
+}
+
+// UpdateEnabled sets the "enabled" field to the value that was provided on create.
+func (u *PaymentProviderInstanceUpsertOne) UpdateEnabled() *PaymentProviderInstanceUpsertOne {
+ return u.Update(func(s *PaymentProviderInstanceUpsert) {
+ s.UpdateEnabled()
+ })
+}
+
+// SetPaymentMode sets the "payment_mode" field.
+func (u *PaymentProviderInstanceUpsertOne) SetPaymentMode(v string) *PaymentProviderInstanceUpsertOne {
+ return u.Update(func(s *PaymentProviderInstanceUpsert) {
+ s.SetPaymentMode(v)
+ })
+}
+
+// UpdatePaymentMode sets the "payment_mode" field to the value that was provided on create.
+func (u *PaymentProviderInstanceUpsertOne) UpdatePaymentMode() *PaymentProviderInstanceUpsertOne {
+ return u.Update(func(s *PaymentProviderInstanceUpsert) {
+ s.UpdatePaymentMode()
+ })
+}
+
+// SetSortOrder sets the "sort_order" field.
+func (u *PaymentProviderInstanceUpsertOne) SetSortOrder(v int) *PaymentProviderInstanceUpsertOne {
+ return u.Update(func(s *PaymentProviderInstanceUpsert) {
+ s.SetSortOrder(v)
+ })
+}
+
+// AddSortOrder adds v to the "sort_order" field.
+func (u *PaymentProviderInstanceUpsertOne) AddSortOrder(v int) *PaymentProviderInstanceUpsertOne {
+ return u.Update(func(s *PaymentProviderInstanceUpsert) {
+ s.AddSortOrder(v)
+ })
+}
+
+// UpdateSortOrder sets the "sort_order" field to the value that was provided on create.
+func (u *PaymentProviderInstanceUpsertOne) UpdateSortOrder() *PaymentProviderInstanceUpsertOne {
+ return u.Update(func(s *PaymentProviderInstanceUpsert) {
+ s.UpdateSortOrder()
+ })
+}
+
+// SetLimits sets the "limits" field.
+func (u *PaymentProviderInstanceUpsertOne) SetLimits(v string) *PaymentProviderInstanceUpsertOne {
+ return u.Update(func(s *PaymentProviderInstanceUpsert) {
+ s.SetLimits(v)
+ })
+}
+
+// UpdateLimits sets the "limits" field to the value that was provided on create.
+func (u *PaymentProviderInstanceUpsertOne) UpdateLimits() *PaymentProviderInstanceUpsertOne {
+ return u.Update(func(s *PaymentProviderInstanceUpsert) {
+ s.UpdateLimits()
+ })
+}
+
+// SetRefundEnabled sets the "refund_enabled" field.
+func (u *PaymentProviderInstanceUpsertOne) SetRefundEnabled(v bool) *PaymentProviderInstanceUpsertOne {
+ return u.Update(func(s *PaymentProviderInstanceUpsert) {
+ s.SetRefundEnabled(v)
+ })
+}
+
+// UpdateRefundEnabled sets the "refund_enabled" field to the value that was provided on create.
+func (u *PaymentProviderInstanceUpsertOne) UpdateRefundEnabled() *PaymentProviderInstanceUpsertOne {
+ return u.Update(func(s *PaymentProviderInstanceUpsert) {
+ s.UpdateRefundEnabled()
+ })
+}
+
+// SetUpdatedAt sets the "updated_at" field.
+func (u *PaymentProviderInstanceUpsertOne) SetUpdatedAt(v time.Time) *PaymentProviderInstanceUpsertOne {
+ return u.Update(func(s *PaymentProviderInstanceUpsert) {
+ s.SetUpdatedAt(v)
+ })
+}
+
+// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create.
+func (u *PaymentProviderInstanceUpsertOne) UpdateUpdatedAt() *PaymentProviderInstanceUpsertOne {
+ return u.Update(func(s *PaymentProviderInstanceUpsert) {
+ s.UpdateUpdatedAt()
+ })
+}
+
+// Exec executes the query.
+func (u *PaymentProviderInstanceUpsertOne) Exec(ctx context.Context) error {
+ if len(u.create.conflict) == 0 {
+ return errors.New("ent: missing options for PaymentProviderInstanceCreate.OnConflict")
+ }
+ return u.create.Exec(ctx)
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (u *PaymentProviderInstanceUpsertOne) 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 *PaymentProviderInstanceUpsertOne) 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 *PaymentProviderInstanceUpsertOne) IDX(ctx context.Context) int64 {
+ id, err := u.ID(ctx)
+ if err != nil {
+ panic(err)
+ }
+ return id
+}
+
+// PaymentProviderInstanceCreateBulk is the builder for creating many PaymentProviderInstance entities in bulk.
+type PaymentProviderInstanceCreateBulk struct {
+ config
+ err error
+ builders []*PaymentProviderInstanceCreate
+ conflict []sql.ConflictOption
+}
+
+// Save creates the PaymentProviderInstance entities in the database.
+func (_c *PaymentProviderInstanceCreateBulk) Save(ctx context.Context) ([]*PaymentProviderInstance, error) {
+ if _c.err != nil {
+ return nil, _c.err
+ }
+ specs := make([]*sqlgraph.CreateSpec, len(_c.builders))
+ nodes := make([]*PaymentProviderInstance, 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.(*PaymentProviderInstanceMutation)
+ 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 *PaymentProviderInstanceCreateBulk) SaveX(ctx context.Context) []*PaymentProviderInstance {
+ v, err := _c.Save(ctx)
+ if err != nil {
+ panic(err)
+ }
+ return v
+}
+
+// Exec executes the query.
+func (_c *PaymentProviderInstanceCreateBulk) Exec(ctx context.Context) error {
+ _, err := _c.Save(ctx)
+ return err
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (_c *PaymentProviderInstanceCreateBulk) 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.PaymentProviderInstance.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.PaymentProviderInstanceUpsert) {
+// SetProviderKey(v+v).
+// }).
+// Exec(ctx)
+func (_c *PaymentProviderInstanceCreateBulk) OnConflict(opts ...sql.ConflictOption) *PaymentProviderInstanceUpsertBulk {
+ _c.conflict = opts
+ return &PaymentProviderInstanceUpsertBulk{
+ create: _c,
+ }
+}
+
+// OnConflictColumns calls `OnConflict` and configures the columns
+// as conflict target. Using this option is equivalent to using:
+//
+// client.PaymentProviderInstance.Create().
+// OnConflict(sql.ConflictColumns(columns...)).
+// Exec(ctx)
+func (_c *PaymentProviderInstanceCreateBulk) OnConflictColumns(columns ...string) *PaymentProviderInstanceUpsertBulk {
+ _c.conflict = append(_c.conflict, sql.ConflictColumns(columns...))
+ return &PaymentProviderInstanceUpsertBulk{
+ create: _c,
+ }
+}
+
+// PaymentProviderInstanceUpsertBulk is the builder for "upsert"-ing
+// a bulk of PaymentProviderInstance nodes.
+type PaymentProviderInstanceUpsertBulk struct {
+ create *PaymentProviderInstanceCreateBulk
+}
+
+// UpdateNewValues updates the mutable fields using the new values that
+// were set on create. Using this option is equivalent to using:
+//
+// client.PaymentProviderInstance.Create().
+// OnConflict(
+// sql.ResolveWithNewValues(),
+// ).
+// Exec(ctx)
+func (u *PaymentProviderInstanceUpsertBulk) UpdateNewValues() *PaymentProviderInstanceUpsertBulk {
+ 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(paymentproviderinstance.FieldCreatedAt)
+ }
+ }
+ }))
+ return u
+}
+
+// Ignore sets each column to itself in case of conflict.
+// Using this option is equivalent to using:
+//
+// client.PaymentProviderInstance.Create().
+// OnConflict(sql.ResolveWithIgnore()).
+// Exec(ctx)
+func (u *PaymentProviderInstanceUpsertBulk) Ignore() *PaymentProviderInstanceUpsertBulk {
+ 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 *PaymentProviderInstanceUpsertBulk) DoNothing() *PaymentProviderInstanceUpsertBulk {
+ u.create.conflict = append(u.create.conflict, sql.DoNothing())
+ return u
+}
+
+// Update allows overriding fields `UPDATE` values. See the PaymentProviderInstanceCreateBulk.OnConflict
+// documentation for more info.
+func (u *PaymentProviderInstanceUpsertBulk) Update(set func(*PaymentProviderInstanceUpsert)) *PaymentProviderInstanceUpsertBulk {
+ u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) {
+ set(&PaymentProviderInstanceUpsert{UpdateSet: update})
+ }))
+ return u
+}
+
+// SetProviderKey sets the "provider_key" field.
+func (u *PaymentProviderInstanceUpsertBulk) SetProviderKey(v string) *PaymentProviderInstanceUpsertBulk {
+ return u.Update(func(s *PaymentProviderInstanceUpsert) {
+ s.SetProviderKey(v)
+ })
+}
+
+// UpdateProviderKey sets the "provider_key" field to the value that was provided on create.
+func (u *PaymentProviderInstanceUpsertBulk) UpdateProviderKey() *PaymentProviderInstanceUpsertBulk {
+ return u.Update(func(s *PaymentProviderInstanceUpsert) {
+ s.UpdateProviderKey()
+ })
+}
+
+// SetName sets the "name" field.
+func (u *PaymentProviderInstanceUpsertBulk) SetName(v string) *PaymentProviderInstanceUpsertBulk {
+ return u.Update(func(s *PaymentProviderInstanceUpsert) {
+ s.SetName(v)
+ })
+}
+
+// UpdateName sets the "name" field to the value that was provided on create.
+func (u *PaymentProviderInstanceUpsertBulk) UpdateName() *PaymentProviderInstanceUpsertBulk {
+ return u.Update(func(s *PaymentProviderInstanceUpsert) {
+ s.UpdateName()
+ })
+}
+
+// SetConfig sets the "config" field.
+func (u *PaymentProviderInstanceUpsertBulk) SetConfig(v string) *PaymentProviderInstanceUpsertBulk {
+ return u.Update(func(s *PaymentProviderInstanceUpsert) {
+ s.SetConfig(v)
+ })
+}
+
+// UpdateConfig sets the "config" field to the value that was provided on create.
+func (u *PaymentProviderInstanceUpsertBulk) UpdateConfig() *PaymentProviderInstanceUpsertBulk {
+ return u.Update(func(s *PaymentProviderInstanceUpsert) {
+ s.UpdateConfig()
+ })
+}
+
+// SetSupportedTypes sets the "supported_types" field.
+func (u *PaymentProviderInstanceUpsertBulk) SetSupportedTypes(v string) *PaymentProviderInstanceUpsertBulk {
+ return u.Update(func(s *PaymentProviderInstanceUpsert) {
+ s.SetSupportedTypes(v)
+ })
+}
+
+// UpdateSupportedTypes sets the "supported_types" field to the value that was provided on create.
+func (u *PaymentProviderInstanceUpsertBulk) UpdateSupportedTypes() *PaymentProviderInstanceUpsertBulk {
+ return u.Update(func(s *PaymentProviderInstanceUpsert) {
+ s.UpdateSupportedTypes()
+ })
+}
+
+// SetEnabled sets the "enabled" field.
+func (u *PaymentProviderInstanceUpsertBulk) SetEnabled(v bool) *PaymentProviderInstanceUpsertBulk {
+ return u.Update(func(s *PaymentProviderInstanceUpsert) {
+ s.SetEnabled(v)
+ })
+}
+
+// UpdateEnabled sets the "enabled" field to the value that was provided on create.
+func (u *PaymentProviderInstanceUpsertBulk) UpdateEnabled() *PaymentProviderInstanceUpsertBulk {
+ return u.Update(func(s *PaymentProviderInstanceUpsert) {
+ s.UpdateEnabled()
+ })
+}
+
+// SetPaymentMode sets the "payment_mode" field.
+func (u *PaymentProviderInstanceUpsertBulk) SetPaymentMode(v string) *PaymentProviderInstanceUpsertBulk {
+ return u.Update(func(s *PaymentProviderInstanceUpsert) {
+ s.SetPaymentMode(v)
+ })
+}
+
+// UpdatePaymentMode sets the "payment_mode" field to the value that was provided on create.
+func (u *PaymentProviderInstanceUpsertBulk) UpdatePaymentMode() *PaymentProviderInstanceUpsertBulk {
+ return u.Update(func(s *PaymentProviderInstanceUpsert) {
+ s.UpdatePaymentMode()
+ })
+}
+
+// SetSortOrder sets the "sort_order" field.
+func (u *PaymentProviderInstanceUpsertBulk) SetSortOrder(v int) *PaymentProviderInstanceUpsertBulk {
+ return u.Update(func(s *PaymentProviderInstanceUpsert) {
+ s.SetSortOrder(v)
+ })
+}
+
+// AddSortOrder adds v to the "sort_order" field.
+func (u *PaymentProviderInstanceUpsertBulk) AddSortOrder(v int) *PaymentProviderInstanceUpsertBulk {
+ return u.Update(func(s *PaymentProviderInstanceUpsert) {
+ s.AddSortOrder(v)
+ })
+}
+
+// UpdateSortOrder sets the "sort_order" field to the value that was provided on create.
+func (u *PaymentProviderInstanceUpsertBulk) UpdateSortOrder() *PaymentProviderInstanceUpsertBulk {
+ return u.Update(func(s *PaymentProviderInstanceUpsert) {
+ s.UpdateSortOrder()
+ })
+}
+
+// SetLimits sets the "limits" field.
+func (u *PaymentProviderInstanceUpsertBulk) SetLimits(v string) *PaymentProviderInstanceUpsertBulk {
+ return u.Update(func(s *PaymentProviderInstanceUpsert) {
+ s.SetLimits(v)
+ })
+}
+
+// UpdateLimits sets the "limits" field to the value that was provided on create.
+func (u *PaymentProviderInstanceUpsertBulk) UpdateLimits() *PaymentProviderInstanceUpsertBulk {
+ return u.Update(func(s *PaymentProviderInstanceUpsert) {
+ s.UpdateLimits()
+ })
+}
+
+// SetRefundEnabled sets the "refund_enabled" field.
+func (u *PaymentProviderInstanceUpsertBulk) SetRefundEnabled(v bool) *PaymentProviderInstanceUpsertBulk {
+ return u.Update(func(s *PaymentProviderInstanceUpsert) {
+ s.SetRefundEnabled(v)
+ })
+}
+
+// UpdateRefundEnabled sets the "refund_enabled" field to the value that was provided on create.
+func (u *PaymentProviderInstanceUpsertBulk) UpdateRefundEnabled() *PaymentProviderInstanceUpsertBulk {
+ return u.Update(func(s *PaymentProviderInstanceUpsert) {
+ s.UpdateRefundEnabled()
+ })
+}
+
+// SetUpdatedAt sets the "updated_at" field.
+func (u *PaymentProviderInstanceUpsertBulk) SetUpdatedAt(v time.Time) *PaymentProviderInstanceUpsertBulk {
+ return u.Update(func(s *PaymentProviderInstanceUpsert) {
+ s.SetUpdatedAt(v)
+ })
+}
+
+// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create.
+func (u *PaymentProviderInstanceUpsertBulk) UpdateUpdatedAt() *PaymentProviderInstanceUpsertBulk {
+ return u.Update(func(s *PaymentProviderInstanceUpsert) {
+ s.UpdateUpdatedAt()
+ })
+}
+
+// Exec executes the query.
+func (u *PaymentProviderInstanceUpsertBulk) 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 PaymentProviderInstanceCreateBulk instead", i)
+ }
+ }
+ if len(u.create.conflict) == 0 {
+ return errors.New("ent: missing options for PaymentProviderInstanceCreateBulk.OnConflict")
+ }
+ return u.create.Exec(ctx)
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (u *PaymentProviderInstanceUpsertBulk) ExecX(ctx context.Context) {
+ if err := u.create.Exec(ctx); err != nil {
+ panic(err)
+ }
+}
diff --git a/backend/ent/paymentproviderinstance_delete.go b/backend/ent/paymentproviderinstance_delete.go
new file mode 100644
index 0000000000000000000000000000000000000000..0cffe7310433c529aaf4e59d478e371eb61b613c
--- /dev/null
+++ b/backend/ent/paymentproviderinstance_delete.go
@@ -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)
+ }
+}
diff --git a/backend/ent/paymentproviderinstance_query.go b/backend/ent/paymentproviderinstance_query.go
new file mode 100644
index 0000000000000000000000000000000000000000..c021208858c7f681ea0449652cd9341cdba7b478
--- /dev/null
+++ b/backend/ent/paymentproviderinstance_query.go
@@ -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)
+}
diff --git a/backend/ent/paymentproviderinstance_update.go b/backend/ent/paymentproviderinstance_update.go
new file mode 100644
index 0000000000000000000000000000000000000000..06dba52740c2e425b0a4b615b2f00e1baacb128d
--- /dev/null
+++ b/backend/ent/paymentproviderinstance_update.go
@@ -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
+}
diff --git a/backend/ent/predicate/predicate.go b/backend/ent/predicate/predicate.go
index a652ab3f1be8dfbe32ba5da438390b5db294f66c..ef551940067ec1a2d96783ca78994bc6cb8d9fae 100644
--- a/backend/ent/predicate/predicate.go
+++ b/backend/ent/predicate/predicate.go
@@ -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)
diff --git a/backend/ent/runtime/runtime.go b/backend/ent/runtime/runtime.go
index 803b7bc24b4552b9b07d70726a56efa261d1cc32..821b7d66e0d80e9889d614b629b6b759735e7965 100644
--- a/backend/ent/runtime/runtime.go
+++ b/backend/ent/runtime/runtime.go
@@ -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"
@@ -28,6 +32,7 @@ import (
"github.com/Wei-Shaw/sub2api/ent/userattributedefinition"
"github.com/Wei-Shaw/sub2api/ent/userattributevalue"
"github.com/Wei-Shaw/sub2api/ent/usersubscription"
+ "github.com/Wei-Shaw/sub2api/internal/domain"
)
// The init function reads all schema descriptors with runtime code
@@ -468,6 +473,10 @@ func init() {
group.DefaultDefaultMappedModel = groupDescDefaultMappedModel.Default.(string)
// group.DefaultMappedModelValidator is a validator for the "default_mapped_model" field. It is called by the builders before save.
group.DefaultMappedModelValidator = groupDescDefaultMappedModel.Validators[0].(func(string) error)
+ // groupDescMessagesDispatchModelConfig is the schema descriptor for messages_dispatch_model_config field.
+ groupDescMessagesDispatchModelConfig := groupFields[26].Descriptor()
+ // group.DefaultMessagesDispatchModelConfig holds the default value on creation for the messages_dispatch_model_config field.
+ group.DefaultMessagesDispatchModelConfig = groupDescMessagesDispatchModelConfig.Default.(domain.OpenAIMessagesDispatchModelConfig)
idempotencyrecordMixin := schema.IdempotencyRecord{}.Mixin()
idempotencyrecordMixinFields0 := idempotencyrecordMixin[0].Fields()
_ = idempotencyrecordMixinFields0
@@ -503,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.
@@ -751,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
diff --git a/backend/ent/schema/group.go b/backend/ent/schema/group.go
index 0eb89c181bbaf65fef4658763b2234afe381f149..d78a68986dea728ef984ecd709c84e7636722d3f 100644
--- a/backend/ent/schema/group.go
+++ b/backend/ent/schema/group.go
@@ -141,6 +141,10 @@ func (Group) Fields() []ent.Field {
MaxLen(100).
Default("").
Comment("默认映射模型 ID,当账号级映射找不到时使用此值"),
+ field.JSON("messages_dispatch_model_config", domain.OpenAIMessagesDispatchModelConfig{}).
+ Default(domain.OpenAIMessagesDispatchModelConfig{}).
+ SchemaType(map[string]string{dialect.Postgres: "jsonb"}).
+ Comment("OpenAI Messages 调度模型配置:按 Claude 系列/精确模型映射到目标 GPT 模型"),
}
}
diff --git a/backend/ent/schema/payment_audit_log.go b/backend/ent/schema/payment_audit_log.go
new file mode 100644
index 0000000000000000000000000000000000000000..7f8a8c04423bf35a8ff1d4711e6aac2b993615f9
--- /dev/null
+++ b/backend/ent/schema/payment_audit_log.go
@@ -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"),
+ }
+}
diff --git a/backend/ent/schema/payment_order.go b/backend/ent/schema/payment_order.go
new file mode 100644
index 0000000000000000000000000000000000000000..a9576d2ab02aca442249de0457ea4321cf15f85e
--- /dev/null
+++ b/backend/ent/schema/payment_order.go
@@ -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"),
+ }
+}
diff --git a/backend/ent/schema/payment_provider_instance.go b/backend/ent/schema/payment_provider_instance.go
new file mode 100644
index 0000000000000000000000000000000000000000..08ab7d31c75dfb513aaf7b5a19d7b538275e214d
--- /dev/null
+++ b/backend/ent/schema/payment_provider_instance.go
@@ -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"),
+ }
+}
diff --git a/backend/ent/schema/subscription_plan.go b/backend/ent/schema/subscription_plan.go
new file mode 100644
index 0000000000000000000000000000000000000000..3e30490bb248181c0c31a95e48f36d9ce7628164
--- /dev/null
+++ b/backend/ent/schema/subscription_plan.go
@@ -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"),
+ }
+}
diff --git a/backend/ent/schema/user.go b/backend/ent/schema/user.go
index d443ef455c1989001c66acf8ebf0dd7929aa2a24..af143d38ed92d31947ec2edc2d6a64a4a37e6f66 100644
--- a/backend/ent/schema/user.go
+++ b/backend/ent/schema/user.go
@@ -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),
}
}
diff --git a/backend/ent/subscriptionplan.go b/backend/ent/subscriptionplan.go
new file mode 100644
index 0000000000000000000000000000000000000000..fa4d7ae3b57463ca49bbc43e22fce335ceaf83f6
--- /dev/null
+++ b/backend/ent/subscriptionplan.go
@@ -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
diff --git a/backend/ent/subscriptionplan/subscriptionplan.go b/backend/ent/subscriptionplan/subscriptionplan.go
new file mode 100644
index 0000000000000000000000000000000000000000..fa125aa714cd4698f60df4b3679a7bcb19e92c7d
--- /dev/null
+++ b/backend/ent/subscriptionplan/subscriptionplan.go
@@ -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()
+}
diff --git a/backend/ent/subscriptionplan/where.go b/backend/ent/subscriptionplan/where.go
new file mode 100644
index 0000000000000000000000000000000000000000..319cfdb5095e2c8de736ed804228e3c96c1640e4
--- /dev/null
+++ b/backend/ent/subscriptionplan/where.go
@@ -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))
+}
diff --git a/backend/ent/subscriptionplan_create.go b/backend/ent/subscriptionplan_create.go
new file mode 100644
index 0000000000000000000000000000000000000000..9109db3a01ea75b6a67aa349c46555164305277b
--- /dev/null
+++ b/backend/ent/subscriptionplan_create.go
@@ -0,0 +1,1317 @@
+// 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/subscriptionplan"
+)
+
+// SubscriptionPlanCreate is the builder for creating a SubscriptionPlan entity.
+type SubscriptionPlanCreate struct {
+ config
+ mutation *SubscriptionPlanMutation
+ hooks []Hook
+ conflict []sql.ConflictOption
+}
+
+// SetGroupID sets the "group_id" field.
+func (_c *SubscriptionPlanCreate) SetGroupID(v int64) *SubscriptionPlanCreate {
+ _c.mutation.SetGroupID(v)
+ return _c
+}
+
+// SetName sets the "name" field.
+func (_c *SubscriptionPlanCreate) SetName(v string) *SubscriptionPlanCreate {
+ _c.mutation.SetName(v)
+ return _c
+}
+
+// SetDescription sets the "description" field.
+func (_c *SubscriptionPlanCreate) SetDescription(v string) *SubscriptionPlanCreate {
+ _c.mutation.SetDescription(v)
+ return _c
+}
+
+// SetNillableDescription sets the "description" field if the given value is not nil.
+func (_c *SubscriptionPlanCreate) SetNillableDescription(v *string) *SubscriptionPlanCreate {
+ if v != nil {
+ _c.SetDescription(*v)
+ }
+ return _c
+}
+
+// SetPrice sets the "price" field.
+func (_c *SubscriptionPlanCreate) SetPrice(v float64) *SubscriptionPlanCreate {
+ _c.mutation.SetPrice(v)
+ return _c
+}
+
+// SetOriginalPrice sets the "original_price" field.
+func (_c *SubscriptionPlanCreate) SetOriginalPrice(v float64) *SubscriptionPlanCreate {
+ _c.mutation.SetOriginalPrice(v)
+ return _c
+}
+
+// SetNillableOriginalPrice sets the "original_price" field if the given value is not nil.
+func (_c *SubscriptionPlanCreate) SetNillableOriginalPrice(v *float64) *SubscriptionPlanCreate {
+ if v != nil {
+ _c.SetOriginalPrice(*v)
+ }
+ return _c
+}
+
+// SetValidityDays sets the "validity_days" field.
+func (_c *SubscriptionPlanCreate) SetValidityDays(v int) *SubscriptionPlanCreate {
+ _c.mutation.SetValidityDays(v)
+ return _c
+}
+
+// SetNillableValidityDays sets the "validity_days" field if the given value is not nil.
+func (_c *SubscriptionPlanCreate) SetNillableValidityDays(v *int) *SubscriptionPlanCreate {
+ if v != nil {
+ _c.SetValidityDays(*v)
+ }
+ return _c
+}
+
+// SetValidityUnit sets the "validity_unit" field.
+func (_c *SubscriptionPlanCreate) SetValidityUnit(v string) *SubscriptionPlanCreate {
+ _c.mutation.SetValidityUnit(v)
+ return _c
+}
+
+// SetNillableValidityUnit sets the "validity_unit" field if the given value is not nil.
+func (_c *SubscriptionPlanCreate) SetNillableValidityUnit(v *string) *SubscriptionPlanCreate {
+ if v != nil {
+ _c.SetValidityUnit(*v)
+ }
+ return _c
+}
+
+// SetFeatures sets the "features" field.
+func (_c *SubscriptionPlanCreate) SetFeatures(v string) *SubscriptionPlanCreate {
+ _c.mutation.SetFeatures(v)
+ return _c
+}
+
+// SetNillableFeatures sets the "features" field if the given value is not nil.
+func (_c *SubscriptionPlanCreate) SetNillableFeatures(v *string) *SubscriptionPlanCreate {
+ if v != nil {
+ _c.SetFeatures(*v)
+ }
+ return _c
+}
+
+// SetProductName sets the "product_name" field.
+func (_c *SubscriptionPlanCreate) SetProductName(v string) *SubscriptionPlanCreate {
+ _c.mutation.SetProductName(v)
+ return _c
+}
+
+// SetNillableProductName sets the "product_name" field if the given value is not nil.
+func (_c *SubscriptionPlanCreate) SetNillableProductName(v *string) *SubscriptionPlanCreate {
+ if v != nil {
+ _c.SetProductName(*v)
+ }
+ return _c
+}
+
+// SetForSale sets the "for_sale" field.
+func (_c *SubscriptionPlanCreate) SetForSale(v bool) *SubscriptionPlanCreate {
+ _c.mutation.SetForSale(v)
+ return _c
+}
+
+// SetNillableForSale sets the "for_sale" field if the given value is not nil.
+func (_c *SubscriptionPlanCreate) SetNillableForSale(v *bool) *SubscriptionPlanCreate {
+ if v != nil {
+ _c.SetForSale(*v)
+ }
+ return _c
+}
+
+// SetSortOrder sets the "sort_order" field.
+func (_c *SubscriptionPlanCreate) SetSortOrder(v int) *SubscriptionPlanCreate {
+ _c.mutation.SetSortOrder(v)
+ return _c
+}
+
+// SetNillableSortOrder sets the "sort_order" field if the given value is not nil.
+func (_c *SubscriptionPlanCreate) SetNillableSortOrder(v *int) *SubscriptionPlanCreate {
+ if v != nil {
+ _c.SetSortOrder(*v)
+ }
+ return _c
+}
+
+// SetCreatedAt sets the "created_at" field.
+func (_c *SubscriptionPlanCreate) SetCreatedAt(v time.Time) *SubscriptionPlanCreate {
+ _c.mutation.SetCreatedAt(v)
+ return _c
+}
+
+// SetNillableCreatedAt sets the "created_at" field if the given value is not nil.
+func (_c *SubscriptionPlanCreate) SetNillableCreatedAt(v *time.Time) *SubscriptionPlanCreate {
+ if v != nil {
+ _c.SetCreatedAt(*v)
+ }
+ return _c
+}
+
+// SetUpdatedAt sets the "updated_at" field.
+func (_c *SubscriptionPlanCreate) SetUpdatedAt(v time.Time) *SubscriptionPlanCreate {
+ _c.mutation.SetUpdatedAt(v)
+ return _c
+}
+
+// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil.
+func (_c *SubscriptionPlanCreate) SetNillableUpdatedAt(v *time.Time) *SubscriptionPlanCreate {
+ if v != nil {
+ _c.SetUpdatedAt(*v)
+ }
+ return _c
+}
+
+// Mutation returns the SubscriptionPlanMutation object of the builder.
+func (_c *SubscriptionPlanCreate) Mutation() *SubscriptionPlanMutation {
+ return _c.mutation
+}
+
+// Save creates the SubscriptionPlan in the database.
+func (_c *SubscriptionPlanCreate) Save(ctx context.Context) (*SubscriptionPlan, error) {
+ _c.defaults()
+ return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks)
+}
+
+// SaveX calls Save and panics if Save returns an error.
+func (_c *SubscriptionPlanCreate) SaveX(ctx context.Context) *SubscriptionPlan {
+ v, err := _c.Save(ctx)
+ if err != nil {
+ panic(err)
+ }
+ return v
+}
+
+// Exec executes the query.
+func (_c *SubscriptionPlanCreate) Exec(ctx context.Context) error {
+ _, err := _c.Save(ctx)
+ return err
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (_c *SubscriptionPlanCreate) 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 *SubscriptionPlanCreate) defaults() {
+ if _, ok := _c.mutation.Description(); !ok {
+ v := subscriptionplan.DefaultDescription
+ _c.mutation.SetDescription(v)
+ }
+ if _, ok := _c.mutation.ValidityDays(); !ok {
+ v := subscriptionplan.DefaultValidityDays
+ _c.mutation.SetValidityDays(v)
+ }
+ if _, ok := _c.mutation.ValidityUnit(); !ok {
+ v := subscriptionplan.DefaultValidityUnit
+ _c.mutation.SetValidityUnit(v)
+ }
+ if _, ok := _c.mutation.Features(); !ok {
+ v := subscriptionplan.DefaultFeatures
+ _c.mutation.SetFeatures(v)
+ }
+ if _, ok := _c.mutation.ProductName(); !ok {
+ v := subscriptionplan.DefaultProductName
+ _c.mutation.SetProductName(v)
+ }
+ if _, ok := _c.mutation.ForSale(); !ok {
+ v := subscriptionplan.DefaultForSale
+ _c.mutation.SetForSale(v)
+ }
+ if _, ok := _c.mutation.SortOrder(); !ok {
+ v := subscriptionplan.DefaultSortOrder
+ _c.mutation.SetSortOrder(v)
+ }
+ if _, ok := _c.mutation.CreatedAt(); !ok {
+ v := subscriptionplan.DefaultCreatedAt()
+ _c.mutation.SetCreatedAt(v)
+ }
+ if _, ok := _c.mutation.UpdatedAt(); !ok {
+ v := subscriptionplan.DefaultUpdatedAt()
+ _c.mutation.SetUpdatedAt(v)
+ }
+}
+
+// check runs all checks and user-defined validators on the builder.
+func (_c *SubscriptionPlanCreate) check() error {
+ if _, ok := _c.mutation.GroupID(); !ok {
+ return &ValidationError{Name: "group_id", err: errors.New(`ent: missing required field "SubscriptionPlan.group_id"`)}
+ }
+ if _, ok := _c.mutation.Name(); !ok {
+ return &ValidationError{Name: "name", err: errors.New(`ent: missing required field "SubscriptionPlan.name"`)}
+ }
+ if v, ok := _c.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 _, ok := _c.mutation.Description(); !ok {
+ return &ValidationError{Name: "description", err: errors.New(`ent: missing required field "SubscriptionPlan.description"`)}
+ }
+ if _, ok := _c.mutation.Price(); !ok {
+ return &ValidationError{Name: "price", err: errors.New(`ent: missing required field "SubscriptionPlan.price"`)}
+ }
+ if _, ok := _c.mutation.ValidityDays(); !ok {
+ return &ValidationError{Name: "validity_days", err: errors.New(`ent: missing required field "SubscriptionPlan.validity_days"`)}
+ }
+ if _, ok := _c.mutation.ValidityUnit(); !ok {
+ return &ValidationError{Name: "validity_unit", err: errors.New(`ent: missing required field "SubscriptionPlan.validity_unit"`)}
+ }
+ if v, ok := _c.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 _, ok := _c.mutation.Features(); !ok {
+ return &ValidationError{Name: "features", err: errors.New(`ent: missing required field "SubscriptionPlan.features"`)}
+ }
+ if _, ok := _c.mutation.ProductName(); !ok {
+ return &ValidationError{Name: "product_name", err: errors.New(`ent: missing required field "SubscriptionPlan.product_name"`)}
+ }
+ if v, ok := _c.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)}
+ }
+ }
+ if _, ok := _c.mutation.ForSale(); !ok {
+ return &ValidationError{Name: "for_sale", err: errors.New(`ent: missing required field "SubscriptionPlan.for_sale"`)}
+ }
+ if _, ok := _c.mutation.SortOrder(); !ok {
+ return &ValidationError{Name: "sort_order", err: errors.New(`ent: missing required field "SubscriptionPlan.sort_order"`)}
+ }
+ if _, ok := _c.mutation.CreatedAt(); !ok {
+ return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "SubscriptionPlan.created_at"`)}
+ }
+ if _, ok := _c.mutation.UpdatedAt(); !ok {
+ return &ValidationError{Name: "updated_at", err: errors.New(`ent: missing required field "SubscriptionPlan.updated_at"`)}
+ }
+ return nil
+}
+
+func (_c *SubscriptionPlanCreate) sqlSave(ctx context.Context) (*SubscriptionPlan, 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 *SubscriptionPlanCreate) createSpec() (*SubscriptionPlan, *sqlgraph.CreateSpec) {
+ var (
+ _node = &SubscriptionPlan{config: _c.config}
+ _spec = sqlgraph.NewCreateSpec(subscriptionplan.Table, sqlgraph.NewFieldSpec(subscriptionplan.FieldID, field.TypeInt64))
+ )
+ _spec.OnConflict = _c.conflict
+ if value, ok := _c.mutation.GroupID(); ok {
+ _spec.SetField(subscriptionplan.FieldGroupID, field.TypeInt64, value)
+ _node.GroupID = value
+ }
+ if value, ok := _c.mutation.Name(); ok {
+ _spec.SetField(subscriptionplan.FieldName, field.TypeString, value)
+ _node.Name = value
+ }
+ if value, ok := _c.mutation.Description(); ok {
+ _spec.SetField(subscriptionplan.FieldDescription, field.TypeString, value)
+ _node.Description = value
+ }
+ if value, ok := _c.mutation.Price(); ok {
+ _spec.SetField(subscriptionplan.FieldPrice, field.TypeFloat64, value)
+ _node.Price = value
+ }
+ if value, ok := _c.mutation.OriginalPrice(); ok {
+ _spec.SetField(subscriptionplan.FieldOriginalPrice, field.TypeFloat64, value)
+ _node.OriginalPrice = &value
+ }
+ if value, ok := _c.mutation.ValidityDays(); ok {
+ _spec.SetField(subscriptionplan.FieldValidityDays, field.TypeInt, value)
+ _node.ValidityDays = value
+ }
+ if value, ok := _c.mutation.ValidityUnit(); ok {
+ _spec.SetField(subscriptionplan.FieldValidityUnit, field.TypeString, value)
+ _node.ValidityUnit = value
+ }
+ if value, ok := _c.mutation.Features(); ok {
+ _spec.SetField(subscriptionplan.FieldFeatures, field.TypeString, value)
+ _node.Features = value
+ }
+ if value, ok := _c.mutation.ProductName(); ok {
+ _spec.SetField(subscriptionplan.FieldProductName, field.TypeString, value)
+ _node.ProductName = value
+ }
+ if value, ok := _c.mutation.ForSale(); ok {
+ _spec.SetField(subscriptionplan.FieldForSale, field.TypeBool, value)
+ _node.ForSale = value
+ }
+ if value, ok := _c.mutation.SortOrder(); ok {
+ _spec.SetField(subscriptionplan.FieldSortOrder, field.TypeInt, value)
+ _node.SortOrder = value
+ }
+ if value, ok := _c.mutation.CreatedAt(); ok {
+ _spec.SetField(subscriptionplan.FieldCreatedAt, field.TypeTime, value)
+ _node.CreatedAt = value
+ }
+ if value, ok := _c.mutation.UpdatedAt(); ok {
+ _spec.SetField(subscriptionplan.FieldUpdatedAt, field.TypeTime, value)
+ _node.UpdatedAt = value
+ }
+ return _node, _spec
+}
+
+// OnConflict allows configuring the `ON CONFLICT` / `ON DUPLICATE KEY` clause
+// of the `INSERT` statement. For example:
+//
+// client.SubscriptionPlan.Create().
+// SetGroupID(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.SubscriptionPlanUpsert) {
+// SetGroupID(v+v).
+// }).
+// Exec(ctx)
+func (_c *SubscriptionPlanCreate) OnConflict(opts ...sql.ConflictOption) *SubscriptionPlanUpsertOne {
+ _c.conflict = opts
+ return &SubscriptionPlanUpsertOne{
+ create: _c,
+ }
+}
+
+// OnConflictColumns calls `OnConflict` and configures the columns
+// as conflict target. Using this option is equivalent to using:
+//
+// client.SubscriptionPlan.Create().
+// OnConflict(sql.ConflictColumns(columns...)).
+// Exec(ctx)
+func (_c *SubscriptionPlanCreate) OnConflictColumns(columns ...string) *SubscriptionPlanUpsertOne {
+ _c.conflict = append(_c.conflict, sql.ConflictColumns(columns...))
+ return &SubscriptionPlanUpsertOne{
+ create: _c,
+ }
+}
+
+type (
+ // SubscriptionPlanUpsertOne is the builder for "upsert"-ing
+ // one SubscriptionPlan node.
+ SubscriptionPlanUpsertOne struct {
+ create *SubscriptionPlanCreate
+ }
+
+ // SubscriptionPlanUpsert is the "OnConflict" setter.
+ SubscriptionPlanUpsert struct {
+ *sql.UpdateSet
+ }
+)
+
+// SetGroupID sets the "group_id" field.
+func (u *SubscriptionPlanUpsert) SetGroupID(v int64) *SubscriptionPlanUpsert {
+ u.Set(subscriptionplan.FieldGroupID, v)
+ return u
+}
+
+// UpdateGroupID sets the "group_id" field to the value that was provided on create.
+func (u *SubscriptionPlanUpsert) UpdateGroupID() *SubscriptionPlanUpsert {
+ u.SetExcluded(subscriptionplan.FieldGroupID)
+ return u
+}
+
+// AddGroupID adds v to the "group_id" field.
+func (u *SubscriptionPlanUpsert) AddGroupID(v int64) *SubscriptionPlanUpsert {
+ u.Add(subscriptionplan.FieldGroupID, v)
+ return u
+}
+
+// SetName sets the "name" field.
+func (u *SubscriptionPlanUpsert) SetName(v string) *SubscriptionPlanUpsert {
+ u.Set(subscriptionplan.FieldName, v)
+ return u
+}
+
+// UpdateName sets the "name" field to the value that was provided on create.
+func (u *SubscriptionPlanUpsert) UpdateName() *SubscriptionPlanUpsert {
+ u.SetExcluded(subscriptionplan.FieldName)
+ return u
+}
+
+// SetDescription sets the "description" field.
+func (u *SubscriptionPlanUpsert) SetDescription(v string) *SubscriptionPlanUpsert {
+ u.Set(subscriptionplan.FieldDescription, v)
+ return u
+}
+
+// UpdateDescription sets the "description" field to the value that was provided on create.
+func (u *SubscriptionPlanUpsert) UpdateDescription() *SubscriptionPlanUpsert {
+ u.SetExcluded(subscriptionplan.FieldDescription)
+ return u
+}
+
+// SetPrice sets the "price" field.
+func (u *SubscriptionPlanUpsert) SetPrice(v float64) *SubscriptionPlanUpsert {
+ u.Set(subscriptionplan.FieldPrice, v)
+ return u
+}
+
+// UpdatePrice sets the "price" field to the value that was provided on create.
+func (u *SubscriptionPlanUpsert) UpdatePrice() *SubscriptionPlanUpsert {
+ u.SetExcluded(subscriptionplan.FieldPrice)
+ return u
+}
+
+// AddPrice adds v to the "price" field.
+func (u *SubscriptionPlanUpsert) AddPrice(v float64) *SubscriptionPlanUpsert {
+ u.Add(subscriptionplan.FieldPrice, v)
+ return u
+}
+
+// SetOriginalPrice sets the "original_price" field.
+func (u *SubscriptionPlanUpsert) SetOriginalPrice(v float64) *SubscriptionPlanUpsert {
+ u.Set(subscriptionplan.FieldOriginalPrice, v)
+ return u
+}
+
+// UpdateOriginalPrice sets the "original_price" field to the value that was provided on create.
+func (u *SubscriptionPlanUpsert) UpdateOriginalPrice() *SubscriptionPlanUpsert {
+ u.SetExcluded(subscriptionplan.FieldOriginalPrice)
+ return u
+}
+
+// AddOriginalPrice adds v to the "original_price" field.
+func (u *SubscriptionPlanUpsert) AddOriginalPrice(v float64) *SubscriptionPlanUpsert {
+ u.Add(subscriptionplan.FieldOriginalPrice, v)
+ return u
+}
+
+// ClearOriginalPrice clears the value of the "original_price" field.
+func (u *SubscriptionPlanUpsert) ClearOriginalPrice() *SubscriptionPlanUpsert {
+ u.SetNull(subscriptionplan.FieldOriginalPrice)
+ return u
+}
+
+// SetValidityDays sets the "validity_days" field.
+func (u *SubscriptionPlanUpsert) SetValidityDays(v int) *SubscriptionPlanUpsert {
+ u.Set(subscriptionplan.FieldValidityDays, v)
+ return u
+}
+
+// UpdateValidityDays sets the "validity_days" field to the value that was provided on create.
+func (u *SubscriptionPlanUpsert) UpdateValidityDays() *SubscriptionPlanUpsert {
+ u.SetExcluded(subscriptionplan.FieldValidityDays)
+ return u
+}
+
+// AddValidityDays adds v to the "validity_days" field.
+func (u *SubscriptionPlanUpsert) AddValidityDays(v int) *SubscriptionPlanUpsert {
+ u.Add(subscriptionplan.FieldValidityDays, v)
+ return u
+}
+
+// SetValidityUnit sets the "validity_unit" field.
+func (u *SubscriptionPlanUpsert) SetValidityUnit(v string) *SubscriptionPlanUpsert {
+ u.Set(subscriptionplan.FieldValidityUnit, v)
+ return u
+}
+
+// UpdateValidityUnit sets the "validity_unit" field to the value that was provided on create.
+func (u *SubscriptionPlanUpsert) UpdateValidityUnit() *SubscriptionPlanUpsert {
+ u.SetExcluded(subscriptionplan.FieldValidityUnit)
+ return u
+}
+
+// SetFeatures sets the "features" field.
+func (u *SubscriptionPlanUpsert) SetFeatures(v string) *SubscriptionPlanUpsert {
+ u.Set(subscriptionplan.FieldFeatures, v)
+ return u
+}
+
+// UpdateFeatures sets the "features" field to the value that was provided on create.
+func (u *SubscriptionPlanUpsert) UpdateFeatures() *SubscriptionPlanUpsert {
+ u.SetExcluded(subscriptionplan.FieldFeatures)
+ return u
+}
+
+// SetProductName sets the "product_name" field.
+func (u *SubscriptionPlanUpsert) SetProductName(v string) *SubscriptionPlanUpsert {
+ u.Set(subscriptionplan.FieldProductName, v)
+ return u
+}
+
+// UpdateProductName sets the "product_name" field to the value that was provided on create.
+func (u *SubscriptionPlanUpsert) UpdateProductName() *SubscriptionPlanUpsert {
+ u.SetExcluded(subscriptionplan.FieldProductName)
+ return u
+}
+
+// SetForSale sets the "for_sale" field.
+func (u *SubscriptionPlanUpsert) SetForSale(v bool) *SubscriptionPlanUpsert {
+ u.Set(subscriptionplan.FieldForSale, v)
+ return u
+}
+
+// UpdateForSale sets the "for_sale" field to the value that was provided on create.
+func (u *SubscriptionPlanUpsert) UpdateForSale() *SubscriptionPlanUpsert {
+ u.SetExcluded(subscriptionplan.FieldForSale)
+ return u
+}
+
+// SetSortOrder sets the "sort_order" field.
+func (u *SubscriptionPlanUpsert) SetSortOrder(v int) *SubscriptionPlanUpsert {
+ u.Set(subscriptionplan.FieldSortOrder, v)
+ return u
+}
+
+// UpdateSortOrder sets the "sort_order" field to the value that was provided on create.
+func (u *SubscriptionPlanUpsert) UpdateSortOrder() *SubscriptionPlanUpsert {
+ u.SetExcluded(subscriptionplan.FieldSortOrder)
+ return u
+}
+
+// AddSortOrder adds v to the "sort_order" field.
+func (u *SubscriptionPlanUpsert) AddSortOrder(v int) *SubscriptionPlanUpsert {
+ u.Add(subscriptionplan.FieldSortOrder, v)
+ return u
+}
+
+// SetUpdatedAt sets the "updated_at" field.
+func (u *SubscriptionPlanUpsert) SetUpdatedAt(v time.Time) *SubscriptionPlanUpsert {
+ u.Set(subscriptionplan.FieldUpdatedAt, v)
+ return u
+}
+
+// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create.
+func (u *SubscriptionPlanUpsert) UpdateUpdatedAt() *SubscriptionPlanUpsert {
+ u.SetExcluded(subscriptionplan.FieldUpdatedAt)
+ return u
+}
+
+// UpdateNewValues updates the mutable fields using the new values that were set on create.
+// Using this option is equivalent to using:
+//
+// client.SubscriptionPlan.Create().
+// OnConflict(
+// sql.ResolveWithNewValues(),
+// ).
+// Exec(ctx)
+func (u *SubscriptionPlanUpsertOne) UpdateNewValues() *SubscriptionPlanUpsertOne {
+ 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(subscriptionplan.FieldCreatedAt)
+ }
+ }))
+ return u
+}
+
+// Ignore sets each column to itself in case of conflict.
+// Using this option is equivalent to using:
+//
+// client.SubscriptionPlan.Create().
+// OnConflict(sql.ResolveWithIgnore()).
+// Exec(ctx)
+func (u *SubscriptionPlanUpsertOne) Ignore() *SubscriptionPlanUpsertOne {
+ 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 *SubscriptionPlanUpsertOne) DoNothing() *SubscriptionPlanUpsertOne {
+ u.create.conflict = append(u.create.conflict, sql.DoNothing())
+ return u
+}
+
+// Update allows overriding fields `UPDATE` values. See the SubscriptionPlanCreate.OnConflict
+// documentation for more info.
+func (u *SubscriptionPlanUpsertOne) Update(set func(*SubscriptionPlanUpsert)) *SubscriptionPlanUpsertOne {
+ u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) {
+ set(&SubscriptionPlanUpsert{UpdateSet: update})
+ }))
+ return u
+}
+
+// SetGroupID sets the "group_id" field.
+func (u *SubscriptionPlanUpsertOne) SetGroupID(v int64) *SubscriptionPlanUpsertOne {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.SetGroupID(v)
+ })
+}
+
+// AddGroupID adds v to the "group_id" field.
+func (u *SubscriptionPlanUpsertOne) AddGroupID(v int64) *SubscriptionPlanUpsertOne {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.AddGroupID(v)
+ })
+}
+
+// UpdateGroupID sets the "group_id" field to the value that was provided on create.
+func (u *SubscriptionPlanUpsertOne) UpdateGroupID() *SubscriptionPlanUpsertOne {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.UpdateGroupID()
+ })
+}
+
+// SetName sets the "name" field.
+func (u *SubscriptionPlanUpsertOne) SetName(v string) *SubscriptionPlanUpsertOne {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.SetName(v)
+ })
+}
+
+// UpdateName sets the "name" field to the value that was provided on create.
+func (u *SubscriptionPlanUpsertOne) UpdateName() *SubscriptionPlanUpsertOne {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.UpdateName()
+ })
+}
+
+// SetDescription sets the "description" field.
+func (u *SubscriptionPlanUpsertOne) SetDescription(v string) *SubscriptionPlanUpsertOne {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.SetDescription(v)
+ })
+}
+
+// UpdateDescription sets the "description" field to the value that was provided on create.
+func (u *SubscriptionPlanUpsertOne) UpdateDescription() *SubscriptionPlanUpsertOne {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.UpdateDescription()
+ })
+}
+
+// SetPrice sets the "price" field.
+func (u *SubscriptionPlanUpsertOne) SetPrice(v float64) *SubscriptionPlanUpsertOne {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.SetPrice(v)
+ })
+}
+
+// AddPrice adds v to the "price" field.
+func (u *SubscriptionPlanUpsertOne) AddPrice(v float64) *SubscriptionPlanUpsertOne {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.AddPrice(v)
+ })
+}
+
+// UpdatePrice sets the "price" field to the value that was provided on create.
+func (u *SubscriptionPlanUpsertOne) UpdatePrice() *SubscriptionPlanUpsertOne {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.UpdatePrice()
+ })
+}
+
+// SetOriginalPrice sets the "original_price" field.
+func (u *SubscriptionPlanUpsertOne) SetOriginalPrice(v float64) *SubscriptionPlanUpsertOne {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.SetOriginalPrice(v)
+ })
+}
+
+// AddOriginalPrice adds v to the "original_price" field.
+func (u *SubscriptionPlanUpsertOne) AddOriginalPrice(v float64) *SubscriptionPlanUpsertOne {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.AddOriginalPrice(v)
+ })
+}
+
+// UpdateOriginalPrice sets the "original_price" field to the value that was provided on create.
+func (u *SubscriptionPlanUpsertOne) UpdateOriginalPrice() *SubscriptionPlanUpsertOne {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.UpdateOriginalPrice()
+ })
+}
+
+// ClearOriginalPrice clears the value of the "original_price" field.
+func (u *SubscriptionPlanUpsertOne) ClearOriginalPrice() *SubscriptionPlanUpsertOne {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.ClearOriginalPrice()
+ })
+}
+
+// SetValidityDays sets the "validity_days" field.
+func (u *SubscriptionPlanUpsertOne) SetValidityDays(v int) *SubscriptionPlanUpsertOne {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.SetValidityDays(v)
+ })
+}
+
+// AddValidityDays adds v to the "validity_days" field.
+func (u *SubscriptionPlanUpsertOne) AddValidityDays(v int) *SubscriptionPlanUpsertOne {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.AddValidityDays(v)
+ })
+}
+
+// UpdateValidityDays sets the "validity_days" field to the value that was provided on create.
+func (u *SubscriptionPlanUpsertOne) UpdateValidityDays() *SubscriptionPlanUpsertOne {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.UpdateValidityDays()
+ })
+}
+
+// SetValidityUnit sets the "validity_unit" field.
+func (u *SubscriptionPlanUpsertOne) SetValidityUnit(v string) *SubscriptionPlanUpsertOne {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.SetValidityUnit(v)
+ })
+}
+
+// UpdateValidityUnit sets the "validity_unit" field to the value that was provided on create.
+func (u *SubscriptionPlanUpsertOne) UpdateValidityUnit() *SubscriptionPlanUpsertOne {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.UpdateValidityUnit()
+ })
+}
+
+// SetFeatures sets the "features" field.
+func (u *SubscriptionPlanUpsertOne) SetFeatures(v string) *SubscriptionPlanUpsertOne {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.SetFeatures(v)
+ })
+}
+
+// UpdateFeatures sets the "features" field to the value that was provided on create.
+func (u *SubscriptionPlanUpsertOne) UpdateFeatures() *SubscriptionPlanUpsertOne {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.UpdateFeatures()
+ })
+}
+
+// SetProductName sets the "product_name" field.
+func (u *SubscriptionPlanUpsertOne) SetProductName(v string) *SubscriptionPlanUpsertOne {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.SetProductName(v)
+ })
+}
+
+// UpdateProductName sets the "product_name" field to the value that was provided on create.
+func (u *SubscriptionPlanUpsertOne) UpdateProductName() *SubscriptionPlanUpsertOne {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.UpdateProductName()
+ })
+}
+
+// SetForSale sets the "for_sale" field.
+func (u *SubscriptionPlanUpsertOne) SetForSale(v bool) *SubscriptionPlanUpsertOne {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.SetForSale(v)
+ })
+}
+
+// UpdateForSale sets the "for_sale" field to the value that was provided on create.
+func (u *SubscriptionPlanUpsertOne) UpdateForSale() *SubscriptionPlanUpsertOne {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.UpdateForSale()
+ })
+}
+
+// SetSortOrder sets the "sort_order" field.
+func (u *SubscriptionPlanUpsertOne) SetSortOrder(v int) *SubscriptionPlanUpsertOne {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.SetSortOrder(v)
+ })
+}
+
+// AddSortOrder adds v to the "sort_order" field.
+func (u *SubscriptionPlanUpsertOne) AddSortOrder(v int) *SubscriptionPlanUpsertOne {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.AddSortOrder(v)
+ })
+}
+
+// UpdateSortOrder sets the "sort_order" field to the value that was provided on create.
+func (u *SubscriptionPlanUpsertOne) UpdateSortOrder() *SubscriptionPlanUpsertOne {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.UpdateSortOrder()
+ })
+}
+
+// SetUpdatedAt sets the "updated_at" field.
+func (u *SubscriptionPlanUpsertOne) SetUpdatedAt(v time.Time) *SubscriptionPlanUpsertOne {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.SetUpdatedAt(v)
+ })
+}
+
+// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create.
+func (u *SubscriptionPlanUpsertOne) UpdateUpdatedAt() *SubscriptionPlanUpsertOne {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.UpdateUpdatedAt()
+ })
+}
+
+// Exec executes the query.
+func (u *SubscriptionPlanUpsertOne) Exec(ctx context.Context) error {
+ if len(u.create.conflict) == 0 {
+ return errors.New("ent: missing options for SubscriptionPlanCreate.OnConflict")
+ }
+ return u.create.Exec(ctx)
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (u *SubscriptionPlanUpsertOne) 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 *SubscriptionPlanUpsertOne) 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 *SubscriptionPlanUpsertOne) IDX(ctx context.Context) int64 {
+ id, err := u.ID(ctx)
+ if err != nil {
+ panic(err)
+ }
+ return id
+}
+
+// SubscriptionPlanCreateBulk is the builder for creating many SubscriptionPlan entities in bulk.
+type SubscriptionPlanCreateBulk struct {
+ config
+ err error
+ builders []*SubscriptionPlanCreate
+ conflict []sql.ConflictOption
+}
+
+// Save creates the SubscriptionPlan entities in the database.
+func (_c *SubscriptionPlanCreateBulk) Save(ctx context.Context) ([]*SubscriptionPlan, error) {
+ if _c.err != nil {
+ return nil, _c.err
+ }
+ specs := make([]*sqlgraph.CreateSpec, len(_c.builders))
+ nodes := make([]*SubscriptionPlan, 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.(*SubscriptionPlanMutation)
+ 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 *SubscriptionPlanCreateBulk) SaveX(ctx context.Context) []*SubscriptionPlan {
+ v, err := _c.Save(ctx)
+ if err != nil {
+ panic(err)
+ }
+ return v
+}
+
+// Exec executes the query.
+func (_c *SubscriptionPlanCreateBulk) Exec(ctx context.Context) error {
+ _, err := _c.Save(ctx)
+ return err
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (_c *SubscriptionPlanCreateBulk) 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.SubscriptionPlan.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.SubscriptionPlanUpsert) {
+// SetGroupID(v+v).
+// }).
+// Exec(ctx)
+func (_c *SubscriptionPlanCreateBulk) OnConflict(opts ...sql.ConflictOption) *SubscriptionPlanUpsertBulk {
+ _c.conflict = opts
+ return &SubscriptionPlanUpsertBulk{
+ create: _c,
+ }
+}
+
+// OnConflictColumns calls `OnConflict` and configures the columns
+// as conflict target. Using this option is equivalent to using:
+//
+// client.SubscriptionPlan.Create().
+// OnConflict(sql.ConflictColumns(columns...)).
+// Exec(ctx)
+func (_c *SubscriptionPlanCreateBulk) OnConflictColumns(columns ...string) *SubscriptionPlanUpsertBulk {
+ _c.conflict = append(_c.conflict, sql.ConflictColumns(columns...))
+ return &SubscriptionPlanUpsertBulk{
+ create: _c,
+ }
+}
+
+// SubscriptionPlanUpsertBulk is the builder for "upsert"-ing
+// a bulk of SubscriptionPlan nodes.
+type SubscriptionPlanUpsertBulk struct {
+ create *SubscriptionPlanCreateBulk
+}
+
+// UpdateNewValues updates the mutable fields using the new values that
+// were set on create. Using this option is equivalent to using:
+//
+// client.SubscriptionPlan.Create().
+// OnConflict(
+// sql.ResolveWithNewValues(),
+// ).
+// Exec(ctx)
+func (u *SubscriptionPlanUpsertBulk) UpdateNewValues() *SubscriptionPlanUpsertBulk {
+ 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(subscriptionplan.FieldCreatedAt)
+ }
+ }
+ }))
+ return u
+}
+
+// Ignore sets each column to itself in case of conflict.
+// Using this option is equivalent to using:
+//
+// client.SubscriptionPlan.Create().
+// OnConflict(sql.ResolveWithIgnore()).
+// Exec(ctx)
+func (u *SubscriptionPlanUpsertBulk) Ignore() *SubscriptionPlanUpsertBulk {
+ 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 *SubscriptionPlanUpsertBulk) DoNothing() *SubscriptionPlanUpsertBulk {
+ u.create.conflict = append(u.create.conflict, sql.DoNothing())
+ return u
+}
+
+// Update allows overriding fields `UPDATE` values. See the SubscriptionPlanCreateBulk.OnConflict
+// documentation for more info.
+func (u *SubscriptionPlanUpsertBulk) Update(set func(*SubscriptionPlanUpsert)) *SubscriptionPlanUpsertBulk {
+ u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) {
+ set(&SubscriptionPlanUpsert{UpdateSet: update})
+ }))
+ return u
+}
+
+// SetGroupID sets the "group_id" field.
+func (u *SubscriptionPlanUpsertBulk) SetGroupID(v int64) *SubscriptionPlanUpsertBulk {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.SetGroupID(v)
+ })
+}
+
+// AddGroupID adds v to the "group_id" field.
+func (u *SubscriptionPlanUpsertBulk) AddGroupID(v int64) *SubscriptionPlanUpsertBulk {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.AddGroupID(v)
+ })
+}
+
+// UpdateGroupID sets the "group_id" field to the value that was provided on create.
+func (u *SubscriptionPlanUpsertBulk) UpdateGroupID() *SubscriptionPlanUpsertBulk {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.UpdateGroupID()
+ })
+}
+
+// SetName sets the "name" field.
+func (u *SubscriptionPlanUpsertBulk) SetName(v string) *SubscriptionPlanUpsertBulk {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.SetName(v)
+ })
+}
+
+// UpdateName sets the "name" field to the value that was provided on create.
+func (u *SubscriptionPlanUpsertBulk) UpdateName() *SubscriptionPlanUpsertBulk {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.UpdateName()
+ })
+}
+
+// SetDescription sets the "description" field.
+func (u *SubscriptionPlanUpsertBulk) SetDescription(v string) *SubscriptionPlanUpsertBulk {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.SetDescription(v)
+ })
+}
+
+// UpdateDescription sets the "description" field to the value that was provided on create.
+func (u *SubscriptionPlanUpsertBulk) UpdateDescription() *SubscriptionPlanUpsertBulk {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.UpdateDescription()
+ })
+}
+
+// SetPrice sets the "price" field.
+func (u *SubscriptionPlanUpsertBulk) SetPrice(v float64) *SubscriptionPlanUpsertBulk {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.SetPrice(v)
+ })
+}
+
+// AddPrice adds v to the "price" field.
+func (u *SubscriptionPlanUpsertBulk) AddPrice(v float64) *SubscriptionPlanUpsertBulk {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.AddPrice(v)
+ })
+}
+
+// UpdatePrice sets the "price" field to the value that was provided on create.
+func (u *SubscriptionPlanUpsertBulk) UpdatePrice() *SubscriptionPlanUpsertBulk {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.UpdatePrice()
+ })
+}
+
+// SetOriginalPrice sets the "original_price" field.
+func (u *SubscriptionPlanUpsertBulk) SetOriginalPrice(v float64) *SubscriptionPlanUpsertBulk {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.SetOriginalPrice(v)
+ })
+}
+
+// AddOriginalPrice adds v to the "original_price" field.
+func (u *SubscriptionPlanUpsertBulk) AddOriginalPrice(v float64) *SubscriptionPlanUpsertBulk {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.AddOriginalPrice(v)
+ })
+}
+
+// UpdateOriginalPrice sets the "original_price" field to the value that was provided on create.
+func (u *SubscriptionPlanUpsertBulk) UpdateOriginalPrice() *SubscriptionPlanUpsertBulk {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.UpdateOriginalPrice()
+ })
+}
+
+// ClearOriginalPrice clears the value of the "original_price" field.
+func (u *SubscriptionPlanUpsertBulk) ClearOriginalPrice() *SubscriptionPlanUpsertBulk {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.ClearOriginalPrice()
+ })
+}
+
+// SetValidityDays sets the "validity_days" field.
+func (u *SubscriptionPlanUpsertBulk) SetValidityDays(v int) *SubscriptionPlanUpsertBulk {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.SetValidityDays(v)
+ })
+}
+
+// AddValidityDays adds v to the "validity_days" field.
+func (u *SubscriptionPlanUpsertBulk) AddValidityDays(v int) *SubscriptionPlanUpsertBulk {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.AddValidityDays(v)
+ })
+}
+
+// UpdateValidityDays sets the "validity_days" field to the value that was provided on create.
+func (u *SubscriptionPlanUpsertBulk) UpdateValidityDays() *SubscriptionPlanUpsertBulk {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.UpdateValidityDays()
+ })
+}
+
+// SetValidityUnit sets the "validity_unit" field.
+func (u *SubscriptionPlanUpsertBulk) SetValidityUnit(v string) *SubscriptionPlanUpsertBulk {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.SetValidityUnit(v)
+ })
+}
+
+// UpdateValidityUnit sets the "validity_unit" field to the value that was provided on create.
+func (u *SubscriptionPlanUpsertBulk) UpdateValidityUnit() *SubscriptionPlanUpsertBulk {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.UpdateValidityUnit()
+ })
+}
+
+// SetFeatures sets the "features" field.
+func (u *SubscriptionPlanUpsertBulk) SetFeatures(v string) *SubscriptionPlanUpsertBulk {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.SetFeatures(v)
+ })
+}
+
+// UpdateFeatures sets the "features" field to the value that was provided on create.
+func (u *SubscriptionPlanUpsertBulk) UpdateFeatures() *SubscriptionPlanUpsertBulk {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.UpdateFeatures()
+ })
+}
+
+// SetProductName sets the "product_name" field.
+func (u *SubscriptionPlanUpsertBulk) SetProductName(v string) *SubscriptionPlanUpsertBulk {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.SetProductName(v)
+ })
+}
+
+// UpdateProductName sets the "product_name" field to the value that was provided on create.
+func (u *SubscriptionPlanUpsertBulk) UpdateProductName() *SubscriptionPlanUpsertBulk {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.UpdateProductName()
+ })
+}
+
+// SetForSale sets the "for_sale" field.
+func (u *SubscriptionPlanUpsertBulk) SetForSale(v bool) *SubscriptionPlanUpsertBulk {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.SetForSale(v)
+ })
+}
+
+// UpdateForSale sets the "for_sale" field to the value that was provided on create.
+func (u *SubscriptionPlanUpsertBulk) UpdateForSale() *SubscriptionPlanUpsertBulk {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.UpdateForSale()
+ })
+}
+
+// SetSortOrder sets the "sort_order" field.
+func (u *SubscriptionPlanUpsertBulk) SetSortOrder(v int) *SubscriptionPlanUpsertBulk {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.SetSortOrder(v)
+ })
+}
+
+// AddSortOrder adds v to the "sort_order" field.
+func (u *SubscriptionPlanUpsertBulk) AddSortOrder(v int) *SubscriptionPlanUpsertBulk {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.AddSortOrder(v)
+ })
+}
+
+// UpdateSortOrder sets the "sort_order" field to the value that was provided on create.
+func (u *SubscriptionPlanUpsertBulk) UpdateSortOrder() *SubscriptionPlanUpsertBulk {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.UpdateSortOrder()
+ })
+}
+
+// SetUpdatedAt sets the "updated_at" field.
+func (u *SubscriptionPlanUpsertBulk) SetUpdatedAt(v time.Time) *SubscriptionPlanUpsertBulk {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.SetUpdatedAt(v)
+ })
+}
+
+// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create.
+func (u *SubscriptionPlanUpsertBulk) UpdateUpdatedAt() *SubscriptionPlanUpsertBulk {
+ return u.Update(func(s *SubscriptionPlanUpsert) {
+ s.UpdateUpdatedAt()
+ })
+}
+
+// Exec executes the query.
+func (u *SubscriptionPlanUpsertBulk) 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 SubscriptionPlanCreateBulk instead", i)
+ }
+ }
+ if len(u.create.conflict) == 0 {
+ return errors.New("ent: missing options for SubscriptionPlanCreateBulk.OnConflict")
+ }
+ return u.create.Exec(ctx)
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (u *SubscriptionPlanUpsertBulk) ExecX(ctx context.Context) {
+ if err := u.create.Exec(ctx); err != nil {
+ panic(err)
+ }
+}
diff --git a/backend/ent/subscriptionplan_delete.go b/backend/ent/subscriptionplan_delete.go
new file mode 100644
index 0000000000000000000000000000000000000000..90c71239e2aebf92198bfd60f213dc9833910892
--- /dev/null
+++ b/backend/ent/subscriptionplan_delete.go
@@ -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)
+ }
+}
diff --git a/backend/ent/subscriptionplan_query.go b/backend/ent/subscriptionplan_query.go
new file mode 100644
index 0000000000000000000000000000000000000000..6c301dcd2948d120ac91e7a9e1a697c432401ec1
--- /dev/null
+++ b/backend/ent/subscriptionplan_query.go
@@ -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)
+}
diff --git a/backend/ent/subscriptionplan_update.go b/backend/ent/subscriptionplan_update.go
new file mode 100644
index 0000000000000000000000000000000000000000..c9225d0f7eae4c52269ba2072b23970d4dcbf047
--- /dev/null
+++ b/backend/ent/subscriptionplan_update.go
@@ -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
+}
diff --git a/backend/ent/tx.go b/backend/ent/tx.go
index b5aea447319be597090ad29c4cf1714f7a0a1736..bb3139d5c84119195c82c071194ca4b8663fbab0 100644
--- a/backend/ent/tx.go
+++ b/backend/ent/tx.go
@@ -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)
diff --git a/backend/ent/user.go b/backend/ent/user.go
index 2435aa1b99874ff2fa33898eb111585223958ec3..a0eef2ba051f6ea9bb5e2e589d2cea2c5c7dfcb4 100644
--- a/backend/ent/user.go
+++ b/backend/ent/user.go
@@ -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)
diff --git a/backend/ent/user/user.go b/backend/ent/user/user.go
index ae9418ff0739e4261d28217b92d0bf010f58bdef..338518a841c6837dc756b57bbeb8d539529a44ef 100644
--- a/backend/ent/user/user.go
+++ b/backend/ent/user/user.go
@@ -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),
diff --git a/backend/ent/user/where.go b/backend/ent/user/where.go
index 1de61037022a66b83d16753f95cac878dae8bcdc..b1d1000f14eafcd3fb94d0497b43320f7c395b9c 100644
--- a/backend/ent/user/where.go
+++ b/backend/ent/user/where.go
@@ -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) {
diff --git a/backend/ent/user_create.go b/backend/ent/user_create.go
index f862a580c524477a23c2e414976e531c47a5c298..7f1c5df141196492b9a0bb05d0fe53fd340c4755 100644
--- a/backend/ent/user_create.go
+++ b/backend/ent/user_create.go
@@ -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
}
diff --git a/backend/ent/user_query.go b/backend/ent/user_query.go
index 4b56e16f43545c9bf852a012e410675215860114..113d87aca24a273a137326557955a6b4eb60b751 100644
--- a/backend/ent/user_query.go
+++ b/backend/ent/user_query.go
@@ -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)
diff --git a/backend/ent/user_update.go b/backend/ent/user_update.go
index 80222c92dc30d4ce8c1ee448cd221653cfa94872..8107c980a8a574c8fcd3ab204260fd3ec9b71c49 100644
--- a/backend/ent/user_update.go
+++ b/backend/ent/user_update.go
@@ -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
diff --git a/backend/go.mod b/backend/go.mod
index c4fc52f130e0c6a0a5caee939f26c8bb9fe1319e..66b6cc25b598efeb750d0d5b36153f8c95bd6863 100644
--- a/backend/go.mod
+++ b/backend/go.mod
@@ -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
diff --git a/backend/go.sum b/backend/go.sum
index 996a4b6db7956b7a1237768cbcb83af1d873c614..e4496f2c0b590e2f17f30a5827fc3b1fc1beef4d 100644
--- a/backend/go.sum
+++ b/backend/go.sum
@@ -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=
diff --git a/backend/internal/config/config.go b/backend/internal/config/config.go
index 9b430377890eca44007b1f4ee4d05a9361c4e8c8..bc4e5e460c3903be71287aa0bd0797af955bbfe7 100644
--- a/backend/internal/config/config.go
+++ b/backend/internal/config/config.go
@@ -65,6 +65,7 @@ type Config struct {
JWT JWTConfig `mapstructure:"jwt"`
Totp TotpConfig `mapstructure:"totp"`
LinuxDo LinuxDoConnectConfig `mapstructure:"linuxdo_connect"`
+ OIDC OIDCConnectConfig `mapstructure:"oidc_connect"`
Default DefaultConfig `mapstructure:"default"`
RateLimit RateLimitConfig `mapstructure:"rate_limit"`
Pricing PricingConfig `mapstructure:"pricing"`
@@ -184,6 +185,34 @@ type LinuxDoConnectConfig struct {
UserInfoUsernamePath string `mapstructure:"userinfo_username_path"`
}
+type OIDCConnectConfig struct {
+ Enabled bool `mapstructure:"enabled"`
+ ProviderName string `mapstructure:"provider_name"` // 显示名: "Keycloak" 等
+ ClientID string `mapstructure:"client_id"`
+ ClientSecret string `mapstructure:"client_secret"`
+ IssuerURL string `mapstructure:"issuer_url"`
+ DiscoveryURL string `mapstructure:"discovery_url"`
+ AuthorizeURL string `mapstructure:"authorize_url"`
+ TokenURL string `mapstructure:"token_url"`
+ UserInfoURL string `mapstructure:"userinfo_url"`
+ JWKSURL string `mapstructure:"jwks_url"`
+ Scopes string `mapstructure:"scopes"` // 默认 "openid email profile"
+ RedirectURL string `mapstructure:"redirect_url"` // 后端回调地址(需在提供方后台登记)
+ FrontendRedirectURL string `mapstructure:"frontend_redirect_url"` // 前端接收 token 的路由(默认:/auth/oidc/callback)
+ TokenAuthMethod string `mapstructure:"token_auth_method"` // client_secret_post / client_secret_basic / none
+ UsePKCE bool `mapstructure:"use_pkce"`
+ ValidateIDToken bool `mapstructure:"validate_id_token"`
+ AllowedSigningAlgs string `mapstructure:"allowed_signing_algs"` // 默认 "RS256,ES256,PS256"
+ ClockSkewSeconds int `mapstructure:"clock_skew_seconds"` // 默认 120
+ RequireEmailVerified bool `mapstructure:"require_email_verified"` // 默认 false
+
+ // 可选:用于从 userinfo JSON 中提取字段的 gjson 路径。
+ // 为空时,服务端会尝试一组常见字段名。
+ UserInfoEmailPath string `mapstructure:"userinfo_email_path"`
+ UserInfoIDPath string `mapstructure:"userinfo_id_path"`
+ UserInfoUsernamePath string `mapstructure:"userinfo_username_path"`
+}
+
// TokenRefreshConfig OAuth token自动刷新配置
type TokenRefreshConfig struct {
// 是否启用自动刷新
@@ -318,6 +347,12 @@ type GatewayConfig struct {
// ForceCodexCLI: 强制将 OpenAI `/v1/responses` 请求按 Codex CLI 处理。
// 用于网关未透传/改写 User-Agent 时的兼容兜底(默认关闭,避免影响其他客户端)。
ForceCodexCLI bool `mapstructure:"force_codex_cli"`
+ // ForcedCodexInstructionsTemplateFile: 服务端强制附加到 Codex 顶层 instructions 的模板文件路径。
+ // 模板渲染后会直接覆盖最终 instructions;若需要保留客户端 system 转换结果,请在模板中显式引用 {{ .ExistingInstructions }}。
+ ForcedCodexInstructionsTemplateFile string `mapstructure:"forced_codex_instructions_template_file"`
+ // ForcedCodexInstructionsTemplate: 启动时从模板文件读取并缓存的模板内容。
+ // 该字段不直接参与配置反序列化,仅用于请求热路径避免重复读盘。
+ ForcedCodexInstructionsTemplate string `mapstructure:"-"`
// OpenAIPassthroughAllowTimeoutHeaders: OpenAI 透传模式是否放行客户端超时头
// 关闭(默认)可避免 x-stainless-timeout 等头导致上游提前断流。
OpenAIPassthroughAllowTimeoutHeaders bool `mapstructure:"openai_passthrough_allow_timeout_headers"`
@@ -620,6 +655,10 @@ type GatewaySchedulingConfig struct {
// 负载计算
LoadBatchEnabled bool `mapstructure:"load_batch_enabled"`
+ // 快照桶读取时的 MGET 分块大小
+ SnapshotMGetChunkSize int `mapstructure:"snapshot_mget_chunk_size"`
+ // 快照重建时的缓存写入分块大小
+ SnapshotWriteChunkSize int `mapstructure:"snapshot_write_chunk_size"`
// 过期槽位清理周期(0 表示禁用)
SlotCleanupInterval time.Duration `mapstructure:"slot_cleanup_interval"`
@@ -968,6 +1007,23 @@ func load(allowMissingJWTSecret bool) (*Config, error) {
cfg.LinuxDo.UserInfoEmailPath = strings.TrimSpace(cfg.LinuxDo.UserInfoEmailPath)
cfg.LinuxDo.UserInfoIDPath = strings.TrimSpace(cfg.LinuxDo.UserInfoIDPath)
cfg.LinuxDo.UserInfoUsernamePath = strings.TrimSpace(cfg.LinuxDo.UserInfoUsernamePath)
+ cfg.OIDC.ProviderName = strings.TrimSpace(cfg.OIDC.ProviderName)
+ cfg.OIDC.ClientID = strings.TrimSpace(cfg.OIDC.ClientID)
+ cfg.OIDC.ClientSecret = strings.TrimSpace(cfg.OIDC.ClientSecret)
+ cfg.OIDC.IssuerURL = strings.TrimSpace(cfg.OIDC.IssuerURL)
+ cfg.OIDC.DiscoveryURL = strings.TrimSpace(cfg.OIDC.DiscoveryURL)
+ cfg.OIDC.AuthorizeURL = strings.TrimSpace(cfg.OIDC.AuthorizeURL)
+ cfg.OIDC.TokenURL = strings.TrimSpace(cfg.OIDC.TokenURL)
+ cfg.OIDC.UserInfoURL = strings.TrimSpace(cfg.OIDC.UserInfoURL)
+ cfg.OIDC.JWKSURL = strings.TrimSpace(cfg.OIDC.JWKSURL)
+ cfg.OIDC.Scopes = strings.TrimSpace(cfg.OIDC.Scopes)
+ cfg.OIDC.RedirectURL = strings.TrimSpace(cfg.OIDC.RedirectURL)
+ cfg.OIDC.FrontendRedirectURL = strings.TrimSpace(cfg.OIDC.FrontendRedirectURL)
+ cfg.OIDC.TokenAuthMethod = strings.ToLower(strings.TrimSpace(cfg.OIDC.TokenAuthMethod))
+ cfg.OIDC.AllowedSigningAlgs = strings.TrimSpace(cfg.OIDC.AllowedSigningAlgs)
+ cfg.OIDC.UserInfoEmailPath = strings.TrimSpace(cfg.OIDC.UserInfoEmailPath)
+ cfg.OIDC.UserInfoIDPath = strings.TrimSpace(cfg.OIDC.UserInfoIDPath)
+ cfg.OIDC.UserInfoUsernamePath = strings.TrimSpace(cfg.OIDC.UserInfoUsernamePath)
cfg.Dashboard.KeyPrefix = strings.TrimSpace(cfg.Dashboard.KeyPrefix)
cfg.CORS.AllowedOrigins = normalizeStringSlice(cfg.CORS.AllowedOrigins)
cfg.Security.ResponseHeaders.AdditionalAllowed = normalizeStringSlice(cfg.Security.ResponseHeaders.AdditionalAllowed)
@@ -979,6 +1035,14 @@ func load(allowMissingJWTSecret bool) (*Config, error) {
cfg.Log.Environment = strings.TrimSpace(cfg.Log.Environment)
cfg.Log.StacktraceLevel = strings.ToLower(strings.TrimSpace(cfg.Log.StacktraceLevel))
cfg.Log.Output.FilePath = strings.TrimSpace(cfg.Log.Output.FilePath)
+ cfg.Gateway.ForcedCodexInstructionsTemplateFile = strings.TrimSpace(cfg.Gateway.ForcedCodexInstructionsTemplateFile)
+ if cfg.Gateway.ForcedCodexInstructionsTemplateFile != "" {
+ content, err := os.ReadFile(cfg.Gateway.ForcedCodexInstructionsTemplateFile)
+ if err != nil {
+ return nil, fmt.Errorf("read forced codex instructions template %q: %w", cfg.Gateway.ForcedCodexInstructionsTemplateFile, err)
+ }
+ cfg.Gateway.ForcedCodexInstructionsTemplate = string(content)
+ }
// 兼容旧键 gateway.openai_ws.sticky_previous_response_ttl_seconds。
// 新键未配置(<=0)时回退旧键;新键优先。
@@ -1138,6 +1202,30 @@ func setDefaults() {
viper.SetDefault("linuxdo_connect.userinfo_id_path", "")
viper.SetDefault("linuxdo_connect.userinfo_username_path", "")
+ // Generic OIDC OAuth 登录
+ viper.SetDefault("oidc_connect.enabled", false)
+ viper.SetDefault("oidc_connect.provider_name", "OIDC")
+ viper.SetDefault("oidc_connect.client_id", "")
+ viper.SetDefault("oidc_connect.client_secret", "")
+ viper.SetDefault("oidc_connect.issuer_url", "")
+ viper.SetDefault("oidc_connect.discovery_url", "")
+ viper.SetDefault("oidc_connect.authorize_url", "")
+ viper.SetDefault("oidc_connect.token_url", "")
+ viper.SetDefault("oidc_connect.userinfo_url", "")
+ viper.SetDefault("oidc_connect.jwks_url", "")
+ viper.SetDefault("oidc_connect.scopes", "openid email profile")
+ viper.SetDefault("oidc_connect.redirect_url", "")
+ viper.SetDefault("oidc_connect.frontend_redirect_url", "/auth/oidc/callback")
+ viper.SetDefault("oidc_connect.token_auth_method", "client_secret_post")
+ viper.SetDefault("oidc_connect.use_pkce", false)
+ viper.SetDefault("oidc_connect.validate_id_token", true)
+ viper.SetDefault("oidc_connect.allowed_signing_algs", "RS256,ES256,PS256")
+ viper.SetDefault("oidc_connect.clock_skew_seconds", 120)
+ viper.SetDefault("oidc_connect.require_email_verified", false)
+ viper.SetDefault("oidc_connect.userinfo_email_path", "")
+ viper.SetDefault("oidc_connect.userinfo_id_path", "")
+ viper.SetDefault("oidc_connect.userinfo_username_path", "")
+
// Database
viper.SetDefault("database.host", "localhost")
viper.SetDefault("database.port", 5432)
@@ -1340,6 +1428,8 @@ func setDefaults() {
viper.SetDefault("gateway.scheduling.fallback_max_waiting", 100)
viper.SetDefault("gateway.scheduling.fallback_selection_mode", "last_used")
viper.SetDefault("gateway.scheduling.load_batch_enabled", true)
+ viper.SetDefault("gateway.scheduling.snapshot_mget_chunk_size", 128)
+ viper.SetDefault("gateway.scheduling.snapshot_write_chunk_size", 256)
viper.SetDefault("gateway.scheduling.slot_cleanup_interval", 30*time.Second)
viper.SetDefault("gateway.scheduling.db_fallback_enabled", true)
viper.SetDefault("gateway.scheduling.db_fallback_timeout_seconds", 0)
@@ -1572,6 +1662,87 @@ func (c *Config) Validate() error {
warnIfInsecureURL("linuxdo_connect.redirect_url", c.LinuxDo.RedirectURL)
warnIfInsecureURL("linuxdo_connect.frontend_redirect_url", c.LinuxDo.FrontendRedirectURL)
}
+ if c.OIDC.Enabled {
+ if strings.TrimSpace(c.OIDC.ClientID) == "" {
+ return fmt.Errorf("oidc_connect.client_id is required when oidc_connect.enabled=true")
+ }
+ if strings.TrimSpace(c.OIDC.IssuerURL) == "" {
+ return fmt.Errorf("oidc_connect.issuer_url is required when oidc_connect.enabled=true")
+ }
+ if strings.TrimSpace(c.OIDC.RedirectURL) == "" {
+ return fmt.Errorf("oidc_connect.redirect_url is required when oidc_connect.enabled=true")
+ }
+ if strings.TrimSpace(c.OIDC.FrontendRedirectURL) == "" {
+ return fmt.Errorf("oidc_connect.frontend_redirect_url is required when oidc_connect.enabled=true")
+ }
+ if !scopeContainsOpenID(c.OIDC.Scopes) {
+ return fmt.Errorf("oidc_connect.scopes must contain openid")
+ }
+
+ method := strings.ToLower(strings.TrimSpace(c.OIDC.TokenAuthMethod))
+ switch method {
+ case "", "client_secret_post", "client_secret_basic", "none":
+ default:
+ return fmt.Errorf("oidc_connect.token_auth_method must be one of: client_secret_post/client_secret_basic/none")
+ }
+ if method == "none" && !c.OIDC.UsePKCE {
+ return fmt.Errorf("oidc_connect.use_pkce must be true when oidc_connect.token_auth_method=none")
+ }
+ if (method == "" || method == "client_secret_post" || method == "client_secret_basic") &&
+ strings.TrimSpace(c.OIDC.ClientSecret) == "" {
+ return fmt.Errorf("oidc_connect.client_secret is required when oidc_connect.enabled=true and token_auth_method is client_secret_post/client_secret_basic")
+ }
+ if c.OIDC.ClockSkewSeconds < 0 || c.OIDC.ClockSkewSeconds > 600 {
+ return fmt.Errorf("oidc_connect.clock_skew_seconds must be between 0 and 600")
+ }
+ if c.OIDC.ValidateIDToken && strings.TrimSpace(c.OIDC.AllowedSigningAlgs) == "" {
+ return fmt.Errorf("oidc_connect.allowed_signing_algs is required when oidc_connect.validate_id_token=true")
+ }
+
+ if err := ValidateAbsoluteHTTPURL(c.OIDC.IssuerURL); err != nil {
+ return fmt.Errorf("oidc_connect.issuer_url invalid: %w", err)
+ }
+ if v := strings.TrimSpace(c.OIDC.DiscoveryURL); v != "" {
+ if err := ValidateAbsoluteHTTPURL(v); err != nil {
+ return fmt.Errorf("oidc_connect.discovery_url invalid: %w", err)
+ }
+ }
+ if v := strings.TrimSpace(c.OIDC.AuthorizeURL); v != "" {
+ if err := ValidateAbsoluteHTTPURL(v); err != nil {
+ return fmt.Errorf("oidc_connect.authorize_url invalid: %w", err)
+ }
+ }
+ if v := strings.TrimSpace(c.OIDC.TokenURL); v != "" {
+ if err := ValidateAbsoluteHTTPURL(v); err != nil {
+ return fmt.Errorf("oidc_connect.token_url invalid: %w", err)
+ }
+ }
+ if v := strings.TrimSpace(c.OIDC.UserInfoURL); v != "" {
+ if err := ValidateAbsoluteHTTPURL(v); err != nil {
+ return fmt.Errorf("oidc_connect.userinfo_url invalid: %w", err)
+ }
+ }
+ if v := strings.TrimSpace(c.OIDC.JWKSURL); v != "" {
+ if err := ValidateAbsoluteHTTPURL(v); err != nil {
+ return fmt.Errorf("oidc_connect.jwks_url invalid: %w", err)
+ }
+ }
+ if err := ValidateAbsoluteHTTPURL(c.OIDC.RedirectURL); err != nil {
+ return fmt.Errorf("oidc_connect.redirect_url invalid: %w", err)
+ }
+ if err := ValidateFrontendRedirectURL(c.OIDC.FrontendRedirectURL); err != nil {
+ return fmt.Errorf("oidc_connect.frontend_redirect_url invalid: %w", err)
+ }
+
+ warnIfInsecureURL("oidc_connect.issuer_url", c.OIDC.IssuerURL)
+ warnIfInsecureURL("oidc_connect.discovery_url", c.OIDC.DiscoveryURL)
+ warnIfInsecureURL("oidc_connect.authorize_url", c.OIDC.AuthorizeURL)
+ warnIfInsecureURL("oidc_connect.token_url", c.OIDC.TokenURL)
+ warnIfInsecureURL("oidc_connect.userinfo_url", c.OIDC.UserInfoURL)
+ warnIfInsecureURL("oidc_connect.jwks_url", c.OIDC.JWKSURL)
+ warnIfInsecureURL("oidc_connect.redirect_url", c.OIDC.RedirectURL)
+ warnIfInsecureURL("oidc_connect.frontend_redirect_url", c.OIDC.FrontendRedirectURL)
+ }
if c.Billing.CircuitBreaker.Enabled {
if c.Billing.CircuitBreaker.FailureThreshold <= 0 {
return fmt.Errorf("billing.circuit_breaker.failure_threshold must be positive")
@@ -2001,6 +2172,12 @@ func (c *Config) Validate() error {
if c.Gateway.Scheduling.FallbackMaxWaiting <= 0 {
return fmt.Errorf("gateway.scheduling.fallback_max_waiting must be positive")
}
+ if c.Gateway.Scheduling.SnapshotMGetChunkSize <= 0 {
+ return fmt.Errorf("gateway.scheduling.snapshot_mget_chunk_size must be positive")
+ }
+ if c.Gateway.Scheduling.SnapshotWriteChunkSize <= 0 {
+ return fmt.Errorf("gateway.scheduling.snapshot_write_chunk_size must be positive")
+ }
if c.Gateway.Scheduling.SlotCleanupInterval < 0 {
return fmt.Errorf("gateway.scheduling.slot_cleanup_interval must be non-negative")
}
@@ -2184,6 +2361,15 @@ func ValidateFrontendRedirectURL(raw string) error {
return nil
}
+func scopeContainsOpenID(scopes string) bool {
+ for _, scope := range strings.Fields(strings.ToLower(strings.TrimSpace(scopes))) {
+ if scope == "openid" {
+ return true
+ }
+ }
+ return false
+}
+
// isHTTPScheme 检查是否为 HTTP 或 HTTPS 协议
func isHTTPScheme(scheme string) bool {
return strings.EqualFold(scheme, "http") || strings.EqualFold(scheme, "https")
diff --git a/backend/internal/config/config_test.go b/backend/internal/config/config_test.go
index 2de5451ee031f166581e4acc33d9a5fbbef30ed8..fe181a2f30198c82cefa60e8910ace563ba6d009 100644
--- a/backend/internal/config/config_test.go
+++ b/backend/internal/config/config_test.go
@@ -1,6 +1,8 @@
package config
import (
+ "os"
+ "path/filepath"
"strings"
"testing"
"time"
@@ -223,6 +225,23 @@ func TestLoadSchedulingConfigFromEnv(t *testing.T) {
}
}
+func TestLoadForcedCodexInstructionsTemplate(t *testing.T) {
+ resetViperWithJWTSecret(t)
+
+ tempDir := t.TempDir()
+ templatePath := filepath.Join(tempDir, "codex-instructions.md.tmpl")
+ configPath := filepath.Join(tempDir, "config.yaml")
+
+ require.NoError(t, os.WriteFile(templatePath, []byte("server-prefix\n\n{{ .ExistingInstructions }}"), 0o644))
+ require.NoError(t, os.WriteFile(configPath, []byte("gateway:\n forced_codex_instructions_template_file: \""+templatePath+"\"\n"), 0o644))
+ t.Setenv("DATA_DIR", tempDir)
+
+ cfg, err := Load()
+ require.NoError(t, err)
+ require.Equal(t, templatePath, cfg.Gateway.ForcedCodexInstructionsTemplateFile)
+ require.Equal(t, "server-prefix\n\n{{ .ExistingInstructions }}", cfg.Gateway.ForcedCodexInstructionsTemplate)
+}
+
func TestLoadDefaultSecurityToggles(t *testing.T) {
resetViperWithJWTSecret(t)
@@ -351,6 +370,60 @@ func TestValidateLinuxDoPKCERequiredForPublicClient(t *testing.T) {
}
}
+func TestValidateOIDCScopesMustContainOpenID(t *testing.T) {
+ resetViperWithJWTSecret(t)
+
+ cfg, err := Load()
+ if err != nil {
+ t.Fatalf("Load() error: %v", err)
+ }
+
+ cfg.OIDC.Enabled = true
+ cfg.OIDC.ClientID = "oidc-client"
+ cfg.OIDC.ClientSecret = "oidc-secret"
+ cfg.OIDC.IssuerURL = "https://issuer.example.com"
+ cfg.OIDC.AuthorizeURL = "https://issuer.example.com/auth"
+ cfg.OIDC.TokenURL = "https://issuer.example.com/token"
+ cfg.OIDC.JWKSURL = "https://issuer.example.com/jwks"
+ cfg.OIDC.RedirectURL = "https://example.com/api/v1/auth/oauth/oidc/callback"
+ cfg.OIDC.FrontendRedirectURL = "/auth/oidc/callback"
+ cfg.OIDC.Scopes = "profile email"
+
+ err = cfg.Validate()
+ if err == nil {
+ t.Fatalf("Validate() expected error when scopes do not include openid, got nil")
+ }
+ if !strings.Contains(err.Error(), "oidc_connect.scopes") {
+ t.Fatalf("Validate() expected oidc_connect.scopes error, got: %v", err)
+ }
+}
+
+func TestValidateOIDCAllowsIssuerOnlyEndpointsWithDiscoveryFallback(t *testing.T) {
+ resetViperWithJWTSecret(t)
+
+ cfg, err := Load()
+ if err != nil {
+ t.Fatalf("Load() error: %v", err)
+ }
+
+ cfg.OIDC.Enabled = true
+ cfg.OIDC.ClientID = "oidc-client"
+ cfg.OIDC.ClientSecret = "oidc-secret"
+ cfg.OIDC.IssuerURL = "https://issuer.example.com"
+ cfg.OIDC.AuthorizeURL = ""
+ cfg.OIDC.TokenURL = ""
+ cfg.OIDC.JWKSURL = ""
+ cfg.OIDC.RedirectURL = "https://example.com/api/v1/auth/oauth/oidc/callback"
+ cfg.OIDC.FrontendRedirectURL = "/auth/oidc/callback"
+ cfg.OIDC.Scopes = "openid email profile"
+ cfg.OIDC.ValidateIDToken = true
+
+ err = cfg.Validate()
+ if err != nil {
+ t.Fatalf("Validate() expected issuer-only OIDC config to pass with discovery fallback, got: %v", err)
+ }
+}
+
func TestLoadDefaultDashboardCacheConfig(t *testing.T) {
resetViperWithJWTSecret(t)
diff --git a/backend/internal/domain/openai_messages_dispatch.go b/backend/internal/domain/openai_messages_dispatch.go
new file mode 100644
index 0000000000000000000000000000000000000000..6b018f1c30581dad66180f6e71728a45365a38ec
--- /dev/null
+++ b/backend/internal/domain/openai_messages_dispatch.go
@@ -0,0 +1,10 @@
+package domain
+
+// OpenAIMessagesDispatchModelConfig controls how Anthropic /v1/messages
+// requests are mapped onto OpenAI/Codex models.
+type OpenAIMessagesDispatchModelConfig struct {
+ OpusMappedModel string `json:"opus_mapped_model,omitempty"`
+ SonnetMappedModel string `json:"sonnet_mapped_model,omitempty"`
+ HaikuMappedModel string `json:"haiku_mapped_model,omitempty"`
+ ExactModelMappings map[string]string `json:"exact_model_mappings,omitempty"`
+}
diff --git a/backend/internal/handler/admin/account_data.go b/backend/internal/handler/admin/account_data.go
index 20cc09eebcafc3dd5a786e1ea996d1948128f459..00da48212aabe095c13e013f5f3c12a84746c162 100644
--- a/backend/internal/handler/admin/account_data.go
+++ b/backend/internal/handler/admin/account_data.go
@@ -10,6 +10,7 @@ import (
"log/slog"
+ infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
"github.com/Wei-Shaw/sub2api/internal/pkg/openai"
"github.com/Wei-Shaw/sub2api/internal/pkg/response"
"github.com/Wei-Shaw/sub2api/internal/service"
@@ -359,7 +360,7 @@ func (h *AccountHandler) listAllProxies(ctx context.Context) ([]service.Proxy, e
pageSize := dataPageCap
var out []service.Proxy
for {
- items, total, err := h.adminService.ListProxies(ctx, page, pageSize, "", "", "")
+ items, total, err := h.adminService.ListProxies(ctx, page, pageSize, "", "", "", "created_at", "desc")
if err != nil {
return nil, err
}
@@ -372,12 +373,12 @@ func (h *AccountHandler) listAllProxies(ctx context.Context) ([]service.Proxy, e
return out, nil
}
-func (h *AccountHandler) listAccountsFiltered(ctx context.Context, platform, accountType, status, search string) ([]service.Account, error) {
+func (h *AccountHandler) listAccountsFiltered(ctx context.Context, platform, accountType, status, search string, groupID int64, privacyMode, sortBy, sortOrder string) ([]service.Account, error) {
page := 1
pageSize := dataPageCap
var out []service.Account
for {
- items, total, err := h.adminService.ListAccounts(ctx, page, pageSize, platform, accountType, status, search, 0, "")
+ items, total, err := h.adminService.ListAccounts(ctx, page, pageSize, platform, accountType, status, search, groupID, privacyMode, sortBy, sortOrder)
if err != nil {
return nil, err
}
@@ -409,11 +410,28 @@ func (h *AccountHandler) resolveExportAccounts(ctx context.Context, ids []int64,
platform := c.Query("platform")
accountType := c.Query("type")
status := c.Query("status")
+ privacyMode := strings.TrimSpace(c.Query("privacy_mode"))
search := strings.TrimSpace(c.Query("search"))
+ sortBy := c.DefaultQuery("sort_by", "name")
+ sortOrder := c.DefaultQuery("sort_order", "asc")
if len(search) > 100 {
search = search[:100]
}
- return h.listAccountsFiltered(ctx, platform, accountType, status, search)
+
+ groupID := int64(0)
+ if groupIDStr := c.Query("group"); groupIDStr != "" {
+ if groupIDStr == accountListGroupUngroupedQueryValue {
+ groupID = service.AccountListGroupUngrouped
+ } else {
+ parsedGroupID, parseErr := strconv.ParseInt(groupIDStr, 10, 64)
+ if parseErr != nil || parsedGroupID <= 0 {
+ return nil, infraerrors.BadRequest("INVALID_GROUP_FILTER", "invalid group filter")
+ }
+ groupID = parsedGroupID
+ }
+ }
+
+ return h.listAccountsFiltered(ctx, platform, accountType, status, search, groupID, privacyMode, sortBy, sortOrder)
}
func (h *AccountHandler) resolveExportProxies(ctx context.Context, accounts []service.Account) ([]service.Proxy, error) {
diff --git a/backend/internal/handler/admin/account_data_handler_test.go b/backend/internal/handler/admin/account_data_handler_test.go
index 285033a17d49f199302fe8253d97fa249b2ea6e3..5793983cba35858c9c8482f430b282d37ce7150b 100644
--- a/backend/internal/handler/admin/account_data_handler_test.go
+++ b/backend/internal/handler/admin/account_data_handler_test.go
@@ -172,6 +172,51 @@ func TestExportDataWithoutProxies(t *testing.T) {
require.Nil(t, resp.Data.Accounts[0].ProxyKey)
}
+func TestExportDataPassesAccountFiltersAndSort(t *testing.T) {
+ router, adminSvc := setupAccountDataRouter()
+ adminSvc.accounts = []service.Account{
+ {ID: 1, Name: "acc-1", Status: service.StatusActive},
+ }
+
+ rec := httptest.NewRecorder()
+ req := httptest.NewRequest(
+ http.MethodGet,
+ "/api/v1/admin/accounts/data?platform=openai&type=oauth&status=active&group=12&privacy_mode=blocked&search=keyword&sort_by=priority&sort_order=desc",
+ nil,
+ )
+ router.ServeHTTP(rec, req)
+ require.Equal(t, http.StatusOK, rec.Code)
+
+ require.Equal(t, 1, adminSvc.lastListAccounts.calls)
+ require.Equal(t, "openai", adminSvc.lastListAccounts.platform)
+ require.Equal(t, "oauth", adminSvc.lastListAccounts.accountType)
+ require.Equal(t, "active", adminSvc.lastListAccounts.status)
+ require.Equal(t, int64(12), adminSvc.lastListAccounts.groupID)
+ require.Equal(t, "blocked", adminSvc.lastListAccounts.privacyMode)
+ require.Equal(t, "keyword", adminSvc.lastListAccounts.search)
+ require.Equal(t, "priority", adminSvc.lastListAccounts.sortBy)
+ require.Equal(t, "desc", adminSvc.lastListAccounts.sortOrder)
+}
+
+func TestExportDataSelectedIDsOverrideFilters(t *testing.T) {
+ router, adminSvc := setupAccountDataRouter()
+
+ rec := httptest.NewRecorder()
+ req := httptest.NewRequest(
+ http.MethodGet,
+ "/api/v1/admin/accounts/data?ids=1,2&platform=openai&search=keyword&sort_by=priority&sort_order=desc",
+ nil,
+ )
+ router.ServeHTTP(rec, req)
+ require.Equal(t, http.StatusOK, rec.Code)
+
+ var resp dataResponse
+ require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp))
+ require.Equal(t, 0, resp.Code)
+ require.Len(t, resp.Data.Accounts, 2)
+ require.Equal(t, 0, adminSvc.lastListAccounts.calls)
+}
+
func TestImportDataReusesProxyAndSkipsDefaultGroup(t *testing.T) {
router, adminSvc := setupAccountDataRouter()
diff --git a/backend/internal/handler/admin/account_handler.go b/backend/internal/handler/admin/account_handler.go
index 9aed64d592b2beaf809fea7fcd55da49048fb488..9e985a797bd985036d77ecf63e1e59aea77daf6d 100644
--- a/backend/internal/handler/admin/account_handler.go
+++ b/backend/internal/handler/admin/account_handler.go
@@ -221,6 +221,8 @@ func (h *AccountHandler) List(c *gin.Context) {
status := c.Query("status")
search := c.Query("search")
privacyMode := strings.TrimSpace(c.Query("privacy_mode"))
+ sortBy := c.DefaultQuery("sort_by", "name")
+ sortOrder := c.DefaultQuery("sort_order", "asc")
// 标准化和验证 search 参数
search = strings.TrimSpace(search)
if len(search) > 100 {
@@ -246,7 +248,7 @@ func (h *AccountHandler) List(c *gin.Context) {
}
}
- accounts, total, err := h.adminService.ListAccounts(c.Request.Context(), page, pageSize, platform, accountType, status, search, groupID, privacyMode)
+ accounts, total, err := h.adminService.ListAccounts(c.Request.Context(), page, pageSize, platform, accountType, status, search, groupID, privacyMode, sortBy, sortOrder)
if err != nil {
response.ErrorFrom(c, err)
return
@@ -2029,7 +2031,7 @@ func (h *AccountHandler) BatchRefreshTier(c *gin.Context) {
accounts := make([]*service.Account, 0)
if len(req.AccountIDs) == 0 {
- allAccounts, _, err := h.adminService.ListAccounts(ctx, 1, 10000, "gemini", "oauth", "", "", 0, "")
+ allAccounts, _, err := h.adminService.ListAccounts(ctx, 1, 10000, "gemini", "oauth", "", "", 0, "", "name", "asc")
if err != nil {
response.ErrorFrom(c, err)
return
diff --git a/backend/internal/handler/admin/admin_service_stub_test.go b/backend/internal/handler/admin/admin_service_stub_test.go
index 60d68913e835232ab4ae5c3bd00d4e380047735a..6d1ef1b6b82426d8d2679808eb16073410cec2e2 100644
--- a/backend/internal/handler/admin/admin_service_stub_test.go
+++ b/backend/internal/handler/admin/admin_service_stub_test.go
@@ -31,6 +31,33 @@ type stubAdminService struct {
platform string
groupIDs []int64
}
+ lastListAccounts struct {
+ platform string
+ accountType string
+ status string
+ search string
+ groupID int64
+ privacyMode string
+ sortBy string
+ sortOrder string
+ calls int
+ }
+ lastListProxies struct {
+ protocol string
+ status string
+ search string
+ sortBy string
+ sortOrder string
+ calls int
+ }
+ lastListRedeemCodes struct {
+ codeType string
+ status string
+ search string
+ sortBy string
+ sortOrder string
+ calls int
+ }
mu sync.Mutex
}
@@ -99,7 +126,7 @@ func newStubAdminService() *stubAdminService {
}
}
-func (s *stubAdminService) ListUsers(ctx context.Context, page, pageSize int, filters service.UserListFilters) ([]service.User, int64, error) {
+func (s *stubAdminService) ListUsers(ctx context.Context, page, pageSize int, filters service.UserListFilters, sortBy, sortOrder string) ([]service.User, int64, error) {
return s.users, int64(len(s.users)), nil
}
@@ -132,7 +159,7 @@ func (s *stubAdminService) UpdateUserBalance(ctx context.Context, userID int64,
return &user, nil
}
-func (s *stubAdminService) GetUserAPIKeys(ctx context.Context, userID int64, page, pageSize int) ([]service.APIKey, int64, error) {
+func (s *stubAdminService) GetUserAPIKeys(ctx context.Context, userID int64, page, pageSize int, sortBy, sortOrder string) ([]service.APIKey, int64, error) {
return s.apiKeys, int64(len(s.apiKeys)), nil
}
@@ -140,7 +167,7 @@ func (s *stubAdminService) GetUserUsageStats(ctx context.Context, userID int64,
return map[string]any{"user_id": userID}, nil
}
-func (s *stubAdminService) ListGroups(ctx context.Context, page, pageSize int, platform, status, search string, isExclusive *bool) ([]service.Group, int64, error) {
+func (s *stubAdminService) ListGroups(ctx context.Context, page, pageSize int, platform, status, search string, isExclusive *bool, sortBy, sortOrder string) ([]service.Group, int64, error) {
return s.groups, int64(len(s.groups)), nil
}
@@ -187,7 +214,16 @@ func (s *stubAdminService) BatchSetGroupRateMultipliers(_ context.Context, _ int
return nil
}
-func (s *stubAdminService) ListAccounts(ctx context.Context, page, pageSize int, platform, accountType, status, search string, groupID int64, privacyMode string) ([]service.Account, int64, error) {
+func (s *stubAdminService) ListAccounts(ctx context.Context, page, pageSize int, platform, accountType, status, search string, groupID int64, privacyMode string, sortBy, sortOrder string) ([]service.Account, int64, error) {
+ s.lastListAccounts.platform = platform
+ s.lastListAccounts.accountType = accountType
+ s.lastListAccounts.status = status
+ s.lastListAccounts.search = search
+ s.lastListAccounts.groupID = groupID
+ s.lastListAccounts.privacyMode = privacyMode
+ s.lastListAccounts.sortBy = sortBy
+ s.lastListAccounts.sortOrder = sortOrder
+ s.lastListAccounts.calls++
return s.accounts, int64(len(s.accounts)), nil
}
@@ -261,7 +297,13 @@ func (s *stubAdminService) CheckMixedChannelRisk(ctx context.Context, currentAcc
return s.checkMixedErr
}
-func (s *stubAdminService) ListProxies(ctx context.Context, page, pageSize int, protocol, status, search string) ([]service.Proxy, int64, error) {
+func (s *stubAdminService) ListProxies(ctx context.Context, page, pageSize int, protocol, status, search string, sortBy, sortOrder string) ([]service.Proxy, int64, error) {
+ s.lastListProxies.protocol = protocol
+ s.lastListProxies.status = status
+ s.lastListProxies.search = search
+ s.lastListProxies.sortBy = sortBy
+ s.lastListProxies.sortOrder = sortOrder
+ s.lastListProxies.calls++
search = strings.TrimSpace(strings.ToLower(search))
filtered := make([]service.Proxy, 0, len(s.proxies))
for _, proxy := range s.proxies {
@@ -283,7 +325,7 @@ func (s *stubAdminService) ListProxies(ctx context.Context, page, pageSize int,
return filtered, int64(len(filtered)), nil
}
-func (s *stubAdminService) ListProxiesWithAccountCount(ctx context.Context, page, pageSize int, protocol, status, search string) ([]service.ProxyWithAccountCount, int64, error) {
+func (s *stubAdminService) ListProxiesWithAccountCount(ctx context.Context, page, pageSize int, protocol, status, search string, sortBy, sortOrder string) ([]service.ProxyWithAccountCount, int64, error) {
return s.proxyCounts, int64(len(s.proxyCounts)), nil
}
@@ -384,7 +426,13 @@ func (s *stubAdminService) CheckProxyQuality(ctx context.Context, id int64) (*se
}, nil
}
-func (s *stubAdminService) ListRedeemCodes(ctx context.Context, page, pageSize int, codeType, status, search string) ([]service.RedeemCode, int64, error) {
+func (s *stubAdminService) ListRedeemCodes(ctx context.Context, page, pageSize int, codeType, status, search string, sortBy, sortOrder string) ([]service.RedeemCode, int64, error) {
+ s.lastListRedeemCodes.codeType = codeType
+ s.lastListRedeemCodes.status = status
+ s.lastListRedeemCodes.search = search
+ s.lastListRedeemCodes.sortBy = sortBy
+ s.lastListRedeemCodes.sortOrder = sortOrder
+ s.lastListRedeemCodes.calls++
return s.redeems, int64(len(s.redeems)), nil
}
diff --git a/backend/internal/handler/admin/announcement_handler.go b/backend/internal/handler/admin/announcement_handler.go
index d1312bc0c702761eceb14b2635e66252bb2dbbe8..d3b9d173731c4b093b770d55e6127ab803e93915 100644
--- a/backend/internal/handler/admin/announcement_handler.go
+++ b/backend/internal/handler/admin/announcement_handler.go
@@ -52,13 +52,17 @@ func (h *AnnouncementHandler) List(c *gin.Context) {
page, pageSize := response.ParsePagination(c)
status := strings.TrimSpace(c.Query("status"))
search := strings.TrimSpace(c.Query("search"))
+ sortBy := c.DefaultQuery("sort_by", "created_at")
+ sortOrder := c.DefaultQuery("sort_order", "desc")
if len(search) > 200 {
search = search[:200]
}
params := pagination.PaginationParams{
- Page: page,
- PageSize: pageSize,
+ Page: page,
+ PageSize: pageSize,
+ SortBy: sortBy,
+ SortOrder: sortOrder,
}
items, paginationResult, err := h.announcementService.List(
@@ -227,8 +231,10 @@ func (h *AnnouncementHandler) ListReadStatus(c *gin.Context) {
page, pageSize := response.ParsePagination(c)
params := pagination.PaginationParams{
- Page: page,
- PageSize: pageSize,
+ Page: page,
+ PageSize: pageSize,
+ SortBy: c.DefaultQuery("sort_by", "email"),
+ SortOrder: c.DefaultQuery("sort_order", "asc"),
}
search := strings.TrimSpace(c.Query("search"))
if len(search) > 200 {
diff --git a/backend/internal/handler/admin/announcement_handler_sort_test.go b/backend/internal/handler/admin/announcement_handler_sort_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..545e619e438ee18d3a5c364727cc9a333c7e1ea4
--- /dev/null
+++ b/backend/internal/handler/admin/announcement_handler_sort_test.go
@@ -0,0 +1,138 @@
+package admin
+
+import (
+ "context"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+ "time"
+
+ "github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
+ "github.com/Wei-Shaw/sub2api/internal/service"
+ "github.com/gin-gonic/gin"
+ "github.com/stretchr/testify/require"
+)
+
+type announcementRepoCapture struct {
+ service.AnnouncementRepository
+ listParams pagination.PaginationParams
+}
+
+func (r *announcementRepoCapture) List(ctx context.Context, params pagination.PaginationParams, filters service.AnnouncementListFilters) ([]service.Announcement, *pagination.PaginationResult, error) {
+ r.listParams = params
+ return []service.Announcement{}, &pagination.PaginationResult{
+ Total: 0,
+ Page: params.Page,
+ PageSize: params.PageSize,
+ Pages: 0,
+ }, nil
+}
+
+func (r *announcementRepoCapture) GetByID(ctx context.Context, id int64) (*service.Announcement, error) {
+ return &service.Announcement{
+ ID: id,
+ Title: "announcement",
+ Content: "content",
+ Status: service.AnnouncementStatusActive,
+ CreatedAt: time.Now(),
+ UpdatedAt: time.Now(),
+ }, nil
+}
+
+type announcementUserRepoCapture struct {
+ service.UserRepository
+ listParams pagination.PaginationParams
+}
+
+func (r *announcementUserRepoCapture) ListWithFilters(ctx context.Context, params pagination.PaginationParams, filters service.UserListFilters) ([]service.User, *pagination.PaginationResult, error) {
+ r.listParams = params
+ return []service.User{}, &pagination.PaginationResult{
+ Total: 0,
+ Page: params.Page,
+ PageSize: params.PageSize,
+ Pages: 0,
+ }, nil
+}
+
+type announcementReadRepoCapture struct {
+ service.AnnouncementReadRepository
+}
+
+func (r *announcementReadRepoCapture) GetReadMapByUsers(ctx context.Context, announcementID int64, userIDs []int64) (map[int64]time.Time, error) {
+ return map[int64]time.Time{}, nil
+}
+
+type announcementUserSubRepoCapture struct {
+ service.UserSubscriptionRepository
+}
+
+func newAnnouncementSortTestRouter(announcementRepo *announcementRepoCapture, userRepo *announcementUserRepoCapture) *gin.Engine {
+ gin.SetMode(gin.TestMode)
+ svc := service.NewAnnouncementService(
+ announcementRepo,
+ &announcementReadRepoCapture{},
+ userRepo,
+ &announcementUserSubRepoCapture{},
+ )
+ handler := NewAnnouncementHandler(svc)
+ router := gin.New()
+ router.GET("/admin/announcements", handler.List)
+ router.GET("/admin/announcements/:id/read-status", handler.ListReadStatus)
+ return router
+}
+
+func TestAdminAnnouncementListSortParams(t *testing.T) {
+ announcementRepo := &announcementRepoCapture{}
+ userRepo := &announcementUserRepoCapture{}
+ router := newAnnouncementSortTestRouter(announcementRepo, userRepo)
+
+ req := httptest.NewRequest(http.MethodGet, "/admin/announcements?sort_by=title&sort_order=ASC", nil)
+ rec := httptest.NewRecorder()
+ router.ServeHTTP(rec, req)
+
+ require.Equal(t, http.StatusOK, rec.Code)
+ require.Equal(t, "title", announcementRepo.listParams.SortBy)
+ require.Equal(t, "ASC", announcementRepo.listParams.SortOrder)
+}
+
+func TestAdminAnnouncementListSortDefaults(t *testing.T) {
+ announcementRepo := &announcementRepoCapture{}
+ userRepo := &announcementUserRepoCapture{}
+ router := newAnnouncementSortTestRouter(announcementRepo, userRepo)
+
+ req := httptest.NewRequest(http.MethodGet, "/admin/announcements", nil)
+ rec := httptest.NewRecorder()
+ router.ServeHTTP(rec, req)
+
+ require.Equal(t, http.StatusOK, rec.Code)
+ require.Equal(t, "created_at", announcementRepo.listParams.SortBy)
+ require.Equal(t, "desc", announcementRepo.listParams.SortOrder)
+}
+
+func TestAdminAnnouncementReadStatusSortParams(t *testing.T) {
+ announcementRepo := &announcementRepoCapture{}
+ userRepo := &announcementUserRepoCapture{}
+ router := newAnnouncementSortTestRouter(announcementRepo, userRepo)
+
+ req := httptest.NewRequest(http.MethodGet, "/admin/announcements/1/read-status?sort_by=balance&sort_order=DESC", nil)
+ rec := httptest.NewRecorder()
+ router.ServeHTTP(rec, req)
+
+ require.Equal(t, http.StatusOK, rec.Code)
+ require.Equal(t, "balance", userRepo.listParams.SortBy)
+ require.Equal(t, "DESC", userRepo.listParams.SortOrder)
+}
+
+func TestAdminAnnouncementReadStatusSortDefaults(t *testing.T) {
+ announcementRepo := &announcementRepoCapture{}
+ userRepo := &announcementUserRepoCapture{}
+ router := newAnnouncementSortTestRouter(announcementRepo, userRepo)
+
+ req := httptest.NewRequest(http.MethodGet, "/admin/announcements/1/read-status", nil)
+ rec := httptest.NewRecorder()
+ router.ServeHTTP(rec, req)
+
+ require.Equal(t, http.StatusOK, rec.Code)
+ require.Equal(t, "email", userRepo.listParams.SortBy)
+ require.Equal(t, "asc", userRepo.listParams.SortOrder)
+}
diff --git a/backend/internal/handler/admin/channel_handler.go b/backend/internal/handler/admin/channel_handler.go
index b503e5c36ed3639deab3953eaff5c36b1b86c224..c92b35bbffb664d724be29b03bab5e6f37156320 100644
--- a/backend/internal/handler/admin/channel_handler.go
+++ b/backend/internal/handler/admin/channel_handler.go
@@ -245,7 +245,12 @@ func (h *ChannelHandler) List(c *gin.Context) {
search = search[:100]
}
- channels, pag, err := h.channelService.List(c.Request.Context(), pagination.PaginationParams{Page: page, PageSize: pageSize}, status, search)
+ channels, pag, err := h.channelService.List(c.Request.Context(), pagination.PaginationParams{
+ Page: page,
+ PageSize: pageSize,
+ SortBy: c.DefaultQuery("sort_by", "created_at"),
+ SortOrder: c.DefaultQuery("sort_order", "desc"),
+ }, status, search)
if err != nil {
response.ErrorFrom(c, err)
return
diff --git a/backend/internal/handler/admin/group_handler.go b/backend/internal/handler/admin/group_handler.go
index 458ed35d47752721eaa031dc726d3eae5a304a1d..cb2bd2018e5dff0cbdb23e06d308a8a5bb16e98d 100644
--- a/backend/internal/handler/admin/group_handler.go
+++ b/backend/internal/handler/admin/group_handler.go
@@ -105,10 +105,11 @@ type CreateGroupRequest struct {
// 支持的模型系列(仅 antigravity 平台使用)
SupportedModelScopes []string `json:"supported_model_scopes"`
// OpenAI Messages 调度配置(仅 openai 平台使用)
- AllowMessagesDispatch bool `json:"allow_messages_dispatch"`
- RequireOAuthOnly bool `json:"require_oauth_only"`
- RequirePrivacySet bool `json:"require_privacy_set"`
- DefaultMappedModel string `json:"default_mapped_model"`
+ AllowMessagesDispatch bool `json:"allow_messages_dispatch"`
+ RequireOAuthOnly bool `json:"require_oauth_only"`
+ RequirePrivacySet bool `json:"require_privacy_set"`
+ DefaultMappedModel string `json:"default_mapped_model"`
+ MessagesDispatchModelConfig service.OpenAIMessagesDispatchModelConfig `json:"messages_dispatch_model_config"`
// 从指定分组复制账号(创建后自动绑定)
CopyAccountsFromGroupIDs []int64 `json:"copy_accounts_from_group_ids"`
}
@@ -139,10 +140,11 @@ type UpdateGroupRequest struct {
// 支持的模型系列(仅 antigravity 平台使用)
SupportedModelScopes *[]string `json:"supported_model_scopes"`
// OpenAI Messages 调度配置(仅 openai 平台使用)
- AllowMessagesDispatch *bool `json:"allow_messages_dispatch"`
- RequireOAuthOnly *bool `json:"require_oauth_only"`
- RequirePrivacySet *bool `json:"require_privacy_set"`
- DefaultMappedModel *string `json:"default_mapped_model"`
+ AllowMessagesDispatch *bool `json:"allow_messages_dispatch"`
+ RequireOAuthOnly *bool `json:"require_oauth_only"`
+ RequirePrivacySet *bool `json:"require_privacy_set"`
+ DefaultMappedModel *string `json:"default_mapped_model"`
+ MessagesDispatchModelConfig *service.OpenAIMessagesDispatchModelConfig `json:"messages_dispatch_model_config"`
// 从指定分组复制账号(同步操作:先清空当前分组的账号绑定,再绑定源分组的账号)
CopyAccountsFromGroupIDs []int64 `json:"copy_accounts_from_group_ids"`
}
@@ -160,6 +162,8 @@ func (h *GroupHandler) List(c *gin.Context) {
search = search[:100]
}
isExclusiveStr := c.Query("is_exclusive")
+ sortBy := c.DefaultQuery("sort_by", "sort_order")
+ sortOrder := c.DefaultQuery("sort_order", "asc")
var isExclusive *bool
if isExclusiveStr != "" {
@@ -167,7 +171,7 @@ func (h *GroupHandler) List(c *gin.Context) {
isExclusive = &val
}
- groups, total, err := h.adminService.ListGroups(c.Request.Context(), page, pageSize, platform, status, search, isExclusive)
+ groups, total, err := h.adminService.ListGroups(c.Request.Context(), page, pageSize, platform, status, search, isExclusive, sortBy, sortOrder)
if err != nil {
response.ErrorFrom(c, err)
return
@@ -257,6 +261,7 @@ func (h *GroupHandler) Create(c *gin.Context) {
RequireOAuthOnly: req.RequireOAuthOnly,
RequirePrivacySet: req.RequirePrivacySet,
DefaultMappedModel: req.DefaultMappedModel,
+ MessagesDispatchModelConfig: req.MessagesDispatchModelConfig,
CopyAccountsFromGroupIDs: req.CopyAccountsFromGroupIDs,
})
if err != nil {
@@ -307,6 +312,7 @@ func (h *GroupHandler) Update(c *gin.Context) {
RequireOAuthOnly: req.RequireOAuthOnly,
RequirePrivacySet: req.RequirePrivacySet,
DefaultMappedModel: req.DefaultMappedModel,
+ MessagesDispatchModelConfig: req.MessagesDispatchModelConfig,
CopyAccountsFromGroupIDs: req.CopyAccountsFromGroupIDs,
})
if err != nil {
diff --git a/backend/internal/handler/admin/payment_handler.go b/backend/internal/handler/admin/payment_handler.go
new file mode 100644
index 0000000000000000000000000000000000000000..b0ed6aed8f7fca88c0ed58d7b9744958e70a6766
--- /dev/null
+++ b/backend/internal/handler/admin/payment_handler.go
@@ -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"})
+}
diff --git a/backend/internal/handler/admin/promo_handler.go b/backend/internal/handler/admin/promo_handler.go
index 3eafa3801a7a6ab480fb9d20ecd1b67a37406224..77d5f171650ce9834b599e7d631386e417a43902 100644
--- a/backend/internal/handler/admin/promo_handler.go
+++ b/backend/internal/handler/admin/promo_handler.go
@@ -55,8 +55,10 @@ func (h *PromoHandler) List(c *gin.Context) {
}
params := pagination.PaginationParams{
- Page: page,
- PageSize: pageSize,
+ Page: page,
+ PageSize: pageSize,
+ SortBy: c.DefaultQuery("sort_by", "created_at"),
+ SortOrder: c.DefaultQuery("sort_order", "desc"),
}
codes, paginationResult, err := h.promoService.List(c.Request.Context(), params, status, search)
diff --git a/backend/internal/handler/admin/proxy_data.go b/backend/internal/handler/admin/proxy_data.go
index 72ecd6c13129629b7362f256e7dd4dbc00a8da4d..8149ce3b3ca9a4be77c21aaa96feff36d0b719f4 100644
--- a/backend/internal/handler/admin/proxy_data.go
+++ b/backend/internal/handler/admin/proxy_data.go
@@ -33,11 +33,13 @@ func (h *ProxyHandler) ExportData(c *gin.Context) {
protocol := c.Query("protocol")
status := c.Query("status")
search := strings.TrimSpace(c.Query("search"))
+ sortBy := c.DefaultQuery("sort_by", "id")
+ sortOrder := c.DefaultQuery("sort_order", "desc")
if len(search) > 100 {
search = search[:100]
}
- proxies, err = h.listProxiesFiltered(ctx, protocol, status, search)
+ proxies, err = h.listProxiesFiltered(ctx, protocol, status, search, sortBy, sortOrder)
if err != nil {
response.ErrorFrom(c, err)
return
@@ -89,7 +91,7 @@ func (h *ProxyHandler) ImportData(c *gin.Context) {
ctx := c.Request.Context()
result := DataImportResult{}
- existingProxies, err := h.listProxiesFiltered(ctx, "", "", "")
+ existingProxies, err := h.listProxiesFiltered(ctx, "", "", "", "id", "desc")
if err != nil {
response.ErrorFrom(c, err)
return
@@ -220,18 +222,33 @@ func parseProxyIDs(c *gin.Context) ([]int64, error) {
return ids, nil
}
-func (h *ProxyHandler) listProxiesFiltered(ctx context.Context, protocol, status, search string) ([]service.Proxy, error) {
+func (h *ProxyHandler) listProxiesFiltered(ctx context.Context, protocol, status, search, sortBy, sortOrder string) ([]service.Proxy, error) {
page := 1
pageSize := dataPageCap
var out []service.Proxy
+ sortBy = strings.TrimSpace(sortBy)
+ useAccountCountSort := strings.EqualFold(sortBy, "account_count")
for {
- items, total, err := h.adminService.ListProxies(ctx, page, pageSize, protocol, status, search)
- if err != nil {
- return nil, err
- }
- out = append(out, items...)
- if len(out) >= int(total) || len(items) == 0 {
- break
+ if useAccountCountSort {
+ items, total, err := h.adminService.ListProxiesWithAccountCount(ctx, page, pageSize, protocol, status, search, sortBy, sortOrder)
+ if err != nil {
+ return nil, err
+ }
+ for i := range items {
+ out = append(out, items[i].Proxy)
+ }
+ if len(out) >= int(total) || len(items) == 0 {
+ break
+ }
+ } else {
+ items, total, err := h.adminService.ListProxies(ctx, page, pageSize, protocol, status, search, sortBy, sortOrder)
+ if err != nil {
+ return nil, err
+ }
+ out = append(out, items...)
+ if len(out) >= int(total) || len(items) == 0 {
+ break
+ }
}
page++
}
diff --git a/backend/internal/handler/admin/proxy_data_handler_test.go b/backend/internal/handler/admin/proxy_data_handler_test.go
index 803f9b6135f88419da10b60b9a1c8043698f9b28..8cd035ed3aff4b051b89e70f0eb358e55c9056f2 100644
--- a/backend/internal/handler/admin/proxy_data_handler_test.go
+++ b/backend/internal/handler/admin/proxy_data_handler_test.go
@@ -74,6 +74,10 @@ func TestProxyExportDataRespectsFilters(t *testing.T) {
require.Len(t, resp.Data.Proxies, 1)
require.Len(t, resp.Data.Accounts, 0)
require.Equal(t, "https", resp.Data.Proxies[0].Protocol)
+ require.Equal(t, 1, adminSvc.lastListProxies.calls)
+ require.Equal(t, "https", adminSvc.lastListProxies.protocol)
+ require.Equal(t, "id", adminSvc.lastListProxies.sortBy)
+ require.Equal(t, "desc", adminSvc.lastListProxies.sortOrder)
}
func TestProxyExportDataWithSelectedIDs(t *testing.T) {
@@ -113,6 +117,96 @@ func TestProxyExportDataWithSelectedIDs(t *testing.T) {
require.Len(t, resp.Data.Proxies, 1)
require.Equal(t, "https", resp.Data.Proxies[0].Protocol)
require.Equal(t, "10.0.0.2", resp.Data.Proxies[0].Host)
+ require.Equal(t, 0, adminSvc.lastListProxies.calls)
+}
+
+func TestProxyExportDataPassesSortParams(t *testing.T) {
+ router, adminSvc := setupProxyDataRouter()
+
+ adminSvc.proxies = []service.Proxy{
+ {
+ ID: 1,
+ Name: "proxy-a",
+ Protocol: "http",
+ Host: "127.0.0.1",
+ Port: 8080,
+ Username: "user",
+ Password: "pass",
+ Status: service.StatusActive,
+ },
+ }
+
+ rec := httptest.NewRecorder()
+ req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/proxies/data?protocol=http&status=active&search=proxy&sort_by=name&sort_order=asc", nil)
+ router.ServeHTTP(rec, req)
+ require.Equal(t, http.StatusOK, rec.Code)
+
+ require.Equal(t, 1, adminSvc.lastListProxies.calls)
+ require.Equal(t, "http", adminSvc.lastListProxies.protocol)
+ require.Equal(t, "active", adminSvc.lastListProxies.status)
+ require.Equal(t, "proxy", adminSvc.lastListProxies.search)
+ require.Equal(t, "name", adminSvc.lastListProxies.sortBy)
+ require.Equal(t, "asc", adminSvc.lastListProxies.sortOrder)
+}
+
+func TestProxyExportDataSortByAccountCountUsesAccountCountListing(t *testing.T) {
+ router, adminSvc := setupProxyDataRouter()
+
+ adminSvc.proxies = []service.Proxy{
+ {
+ ID: 1,
+ Name: "proxy-id-1",
+ Protocol: "http",
+ Host: "127.0.0.1",
+ Port: 8080,
+ Status: service.StatusActive,
+ },
+ {
+ ID: 2,
+ Name: "proxy-id-2",
+ Protocol: "http",
+ Host: "127.0.0.2",
+ Port: 8081,
+ Status: service.StatusActive,
+ },
+ }
+ adminSvc.proxyCounts = []service.ProxyWithAccountCount{
+ {
+ Proxy: service.Proxy{
+ ID: 2,
+ Name: "proxy-count-high",
+ Protocol: "http",
+ Host: "127.0.0.2",
+ Port: 8081,
+ Status: service.StatusActive,
+ },
+ AccountCount: 9,
+ },
+ {
+ Proxy: service.Proxy{
+ ID: 1,
+ Name: "proxy-count-low",
+ Protocol: "http",
+ Host: "127.0.0.1",
+ Port: 8080,
+ Status: service.StatusActive,
+ },
+ AccountCount: 1,
+ },
+ }
+
+ rec := httptest.NewRecorder()
+ req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/proxies/data?sort_by=account_count&sort_order=desc", nil)
+ router.ServeHTTP(rec, req)
+ require.Equal(t, http.StatusOK, rec.Code)
+
+ var resp proxyDataResponse
+ require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp))
+ require.Equal(t, 0, resp.Code)
+ require.Len(t, resp.Data.Proxies, 2)
+ require.Equal(t, "proxy-count-high", resp.Data.Proxies[0].Name)
+ require.Equal(t, "proxy-count-low", resp.Data.Proxies[1].Name)
+ require.Equal(t, 0, adminSvc.lastListProxies.calls)
}
func TestProxyImportDataReusesAndTriggersLatencyProbe(t *testing.T) {
diff --git a/backend/internal/handler/admin/proxy_handler.go b/backend/internal/handler/admin/proxy_handler.go
index e8ae0ce2d0e125e127f0bf7aa4062216351e497c..f97fcb0a71a16daad1d99168f2d9ae5d55d9271e 100644
--- a/backend/internal/handler/admin/proxy_handler.go
+++ b/backend/internal/handler/admin/proxy_handler.go
@@ -52,13 +52,15 @@ func (h *ProxyHandler) List(c *gin.Context) {
protocol := c.Query("protocol")
status := c.Query("status")
search := c.Query("search")
+ sortBy := c.DefaultQuery("sort_by", "id")
+ sortOrder := c.DefaultQuery("sort_order", "desc")
// 标准化和验证 search 参数
search = strings.TrimSpace(search)
if len(search) > 100 {
search = search[:100]
}
- proxies, total, err := h.adminService.ListProxiesWithAccountCount(c.Request.Context(), page, pageSize, protocol, status, search)
+ proxies, total, err := h.adminService.ListProxiesWithAccountCount(c.Request.Context(), page, pageSize, protocol, status, search, sortBy, sortOrder)
if err != nil {
response.ErrorFrom(c, err)
return
diff --git a/backend/internal/handler/admin/redeem_export_handler_test.go b/backend/internal/handler/admin/redeem_export_handler_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..9983fe319ca18dbec8d2070f02a813b35ab4fb14
--- /dev/null
+++ b/backend/internal/handler/admin/redeem_export_handler_test.go
@@ -0,0 +1,49 @@
+package admin
+
+import (
+ "net/http"
+ "net/http/httptest"
+ "testing"
+
+ "github.com/gin-gonic/gin"
+ "github.com/stretchr/testify/require"
+)
+
+func setupRedeemExportRouter() (*gin.Engine, *stubAdminService) {
+ gin.SetMode(gin.TestMode)
+ router := gin.New()
+ adminSvc := newStubAdminService()
+
+ h := NewRedeemHandler(adminSvc, nil)
+ router.GET("/api/v1/admin/redeem-codes/export", h.Export)
+ return router, adminSvc
+}
+
+func TestRedeemExportPassesSearchAndSort(t *testing.T) {
+ router, adminSvc := setupRedeemExportRouter()
+
+ rec := httptest.NewRecorder()
+ req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/redeem-codes/export?type=balance&status=unused&search=ABC&sort_by=value&sort_order=asc", nil)
+ router.ServeHTTP(rec, req)
+ require.Equal(t, http.StatusOK, rec.Code)
+
+ require.Equal(t, 1, adminSvc.lastListRedeemCodes.calls)
+ require.Equal(t, "balance", adminSvc.lastListRedeemCodes.codeType)
+ require.Equal(t, "unused", adminSvc.lastListRedeemCodes.status)
+ require.Equal(t, "ABC", adminSvc.lastListRedeemCodes.search)
+ require.Equal(t, "value", adminSvc.lastListRedeemCodes.sortBy)
+ require.Equal(t, "asc", adminSvc.lastListRedeemCodes.sortOrder)
+}
+
+func TestRedeemExportSortDefaults(t *testing.T) {
+ router, adminSvc := setupRedeemExportRouter()
+
+ rec := httptest.NewRecorder()
+ req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/redeem-codes/export", nil)
+ router.ServeHTTP(rec, req)
+ require.Equal(t, http.StatusOK, rec.Code)
+
+ require.Equal(t, 1, adminSvc.lastListRedeemCodes.calls)
+ require.Equal(t, "id", adminSvc.lastListRedeemCodes.sortBy)
+ require.Equal(t, "desc", adminSvc.lastListRedeemCodes.sortOrder)
+}
diff --git a/backend/internal/handler/admin/redeem_handler.go b/backend/internal/handler/admin/redeem_handler.go
index c494e5fb8b5df25dbc1197a55ceed141088c4e19..24365f3da41c2176d7e1e120eaa02d70a846fbaa 100644
--- a/backend/internal/handler/admin/redeem_handler.go
+++ b/backend/internal/handler/admin/redeem_handler.go
@@ -59,13 +59,15 @@ func (h *RedeemHandler) List(c *gin.Context) {
codeType := c.Query("type")
status := c.Query("status")
search := c.Query("search")
+ sortBy := c.DefaultQuery("sort_by", "id")
+ sortOrder := c.DefaultQuery("sort_order", "desc")
// 标准化和验证 search 参数
search = strings.TrimSpace(search)
if len(search) > 100 {
search = search[:100]
}
- codes, total, err := h.adminService.ListRedeemCodes(c.Request.Context(), page, pageSize, codeType, status, search)
+ codes, total, err := h.adminService.ListRedeemCodes(c.Request.Context(), page, pageSize, codeType, status, search, sortBy, sortOrder)
if err != nil {
response.ErrorFrom(c, err)
return
@@ -300,9 +302,15 @@ func (h *RedeemHandler) GetStats(c *gin.Context) {
func (h *RedeemHandler) Export(c *gin.Context) {
codeType := c.Query("type")
status := c.Query("status")
+ search := strings.TrimSpace(c.Query("search"))
+ sortBy := c.DefaultQuery("sort_by", "id")
+ sortOrder := c.DefaultQuery("sort_order", "desc")
+ if len(search) > 100 {
+ search = search[:100]
+ }
// Get all codes without pagination (use large page size)
- codes, _, err := h.adminService.ListRedeemCodes(c.Request.Context(), 1, 10000, codeType, status, "")
+ codes, _, err := h.adminService.ListRedeemCodes(c.Request.Context(), 1, 10000, codeType, status, search, sortBy, sortOrder)
if err != nil {
response.ErrorFrom(c, err)
return
diff --git a/backend/internal/handler/admin/setting_handler.go b/backend/internal/handler/admin/setting_handler.go
index 4cbe5188ec27cb349675b0c107170a70ed11202f..ba7511315c65025dcd657c8a4748a49992347a68 100644
--- a/backend/internal/handler/admin/setting_handler.go
+++ b/backend/internal/handler/admin/setting_handler.go
@@ -35,21 +35,34 @@ func generateMenuItemID() (string, error) {
return hex.EncodeToString(b), nil
}
+func scopesContainOpenID(scopes string) bool {
+ for _, scope := range strings.Fields(strings.ToLower(strings.TrimSpace(scopes))) {
+ if scope == "openid" {
+ return true
+ }
+ }
+ return false
+}
+
// 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,
}
}
@@ -72,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,
@@ -96,6 +118,28 @@ func (h *SettingHandler) GetSettings(c *gin.Context) {
LinuxDoConnectClientID: settings.LinuxDoConnectClientID,
LinuxDoConnectClientSecretConfigured: settings.LinuxDoConnectClientSecretConfigured,
LinuxDoConnectRedirectURL: settings.LinuxDoConnectRedirectURL,
+ OIDCConnectEnabled: settings.OIDCConnectEnabled,
+ OIDCConnectProviderName: settings.OIDCConnectProviderName,
+ OIDCConnectClientID: settings.OIDCConnectClientID,
+ OIDCConnectClientSecretConfigured: settings.OIDCConnectClientSecretConfigured,
+ OIDCConnectIssuerURL: settings.OIDCConnectIssuerURL,
+ OIDCConnectDiscoveryURL: settings.OIDCConnectDiscoveryURL,
+ OIDCConnectAuthorizeURL: settings.OIDCConnectAuthorizeURL,
+ OIDCConnectTokenURL: settings.OIDCConnectTokenURL,
+ OIDCConnectUserInfoURL: settings.OIDCConnectUserInfoURL,
+ OIDCConnectJWKSURL: settings.OIDCConnectJWKSURL,
+ OIDCConnectScopes: settings.OIDCConnectScopes,
+ OIDCConnectRedirectURL: settings.OIDCConnectRedirectURL,
+ OIDCConnectFrontendRedirectURL: settings.OIDCConnectFrontendRedirectURL,
+ OIDCConnectTokenAuthMethod: settings.OIDCConnectTokenAuthMethod,
+ OIDCConnectUsePKCE: settings.OIDCConnectUsePKCE,
+ OIDCConnectValidateIDToken: settings.OIDCConnectValidateIDToken,
+ OIDCConnectAllowedSigningAlgs: settings.OIDCConnectAllowedSigningAlgs,
+ OIDCConnectClockSkewSeconds: settings.OIDCConnectClockSkewSeconds,
+ OIDCConnectRequireEmailVerified: settings.OIDCConnectRequireEmailVerified,
+ OIDCConnectUserInfoEmailPath: settings.OIDCConnectUserInfoEmailPath,
+ OIDCConnectUserInfoIDPath: settings.OIDCConnectUserInfoIDPath,
+ OIDCConnectUserInfoUsernamePath: settings.OIDCConnectUserInfoUsernamePath,
SiteName: settings.SiteName,
SiteLogo: settings.SiteLogo,
SiteSubtitle: settings.SiteSubtitle,
@@ -106,6 +150,8 @@ func (h *SettingHandler) GetSettings(c *gin.Context) {
HideCcsImportButton: settings.HideCcsImportButton,
PurchaseSubscriptionEnabled: settings.PurchaseSubscriptionEnabled,
PurchaseSubscriptionURL: settings.PurchaseSubscriptionURL,
+ TableDefaultPageSize: settings.TableDefaultPageSize,
+ TablePageSizeOptions: settings.TablePageSizeOptions,
CustomMenuItems: dto.ParseCustomMenuItems(settings.CustomMenuItems),
CustomEndpoints: dto.ParseCustomEndpoints(settings.CustomEndpoints),
DefaultConcurrency: settings.DefaultConcurrency,
@@ -129,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,
})
}
@@ -164,6 +228,30 @@ type UpdateSettingsRequest struct {
LinuxDoConnectClientSecret string `json:"linuxdo_connect_client_secret"`
LinuxDoConnectRedirectURL string `json:"linuxdo_connect_redirect_url"`
+ // Generic OIDC OAuth 登录
+ OIDCConnectEnabled bool `json:"oidc_connect_enabled"`
+ OIDCConnectProviderName string `json:"oidc_connect_provider_name"`
+ OIDCConnectClientID string `json:"oidc_connect_client_id"`
+ OIDCConnectClientSecret string `json:"oidc_connect_client_secret"`
+ OIDCConnectIssuerURL string `json:"oidc_connect_issuer_url"`
+ OIDCConnectDiscoveryURL string `json:"oidc_connect_discovery_url"`
+ OIDCConnectAuthorizeURL string `json:"oidc_connect_authorize_url"`
+ OIDCConnectTokenURL string `json:"oidc_connect_token_url"`
+ OIDCConnectUserInfoURL string `json:"oidc_connect_userinfo_url"`
+ OIDCConnectJWKSURL string `json:"oidc_connect_jwks_url"`
+ OIDCConnectScopes string `json:"oidc_connect_scopes"`
+ OIDCConnectRedirectURL string `json:"oidc_connect_redirect_url"`
+ OIDCConnectFrontendRedirectURL string `json:"oidc_connect_frontend_redirect_url"`
+ OIDCConnectTokenAuthMethod string `json:"oidc_connect_token_auth_method"`
+ OIDCConnectUsePKCE bool `json:"oidc_connect_use_pkce"`
+ OIDCConnectValidateIDToken bool `json:"oidc_connect_validate_id_token"`
+ OIDCConnectAllowedSigningAlgs string `json:"oidc_connect_allowed_signing_algs"`
+ OIDCConnectClockSkewSeconds int `json:"oidc_connect_clock_skew_seconds"`
+ OIDCConnectRequireEmailVerified bool `json:"oidc_connect_require_email_verified"`
+ OIDCConnectUserInfoEmailPath string `json:"oidc_connect_userinfo_email_path"`
+ OIDCConnectUserInfoIDPath string `json:"oidc_connect_userinfo_id_path"`
+ OIDCConnectUserInfoUsernamePath string `json:"oidc_connect_userinfo_username_path"`
+
// OEM设置
SiteName string `json:"site_name"`
SiteLogo string `json:"site_logo"`
@@ -175,6 +263,8 @@ type UpdateSettingsRequest struct {
HideCcsImportButton bool `json:"hide_ccs_import_button"`
PurchaseSubscriptionEnabled *bool `json:"purchase_subscription_enabled"`
PurchaseSubscriptionURL *string `json:"purchase_subscription_url"`
+ TableDefaultPageSize int `json:"table_default_page_size"`
+ TablePageSizeOptions []int `json:"table_page_size_options"`
CustomMenuItems *[]dto.CustomMenuItem `json:"custom_menu_items"`
CustomEndpoints *[]dto.CustomEndpoint `json:"custom_endpoints"`
@@ -213,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 更新系统设置
@@ -237,6 +349,13 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
if req.DefaultBalance < 0 {
req.DefaultBalance = 0
}
+ // 通用表格配置:兼容旧客户端未传字段时保留当前值。
+ if req.TableDefaultPageSize <= 0 {
+ req.TableDefaultPageSize = previousSettings.TableDefaultPageSize
+ }
+ if req.TablePageSizeOptions == nil {
+ req.TablePageSizeOptions = previousSettings.TablePageSizeOptions
+ }
req.SMTPHost = strings.TrimSpace(req.SMTPHost)
req.SMTPUsername = strings.TrimSpace(req.SMTPUsername)
req.SMTPPassword = strings.TrimSpace(req.SMTPPassword)
@@ -324,6 +443,122 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
}
}
+ // Generic OIDC 参数验证
+ if req.OIDCConnectEnabled {
+ req.OIDCConnectProviderName = strings.TrimSpace(req.OIDCConnectProviderName)
+ req.OIDCConnectClientID = strings.TrimSpace(req.OIDCConnectClientID)
+ req.OIDCConnectClientSecret = strings.TrimSpace(req.OIDCConnectClientSecret)
+ req.OIDCConnectIssuerURL = strings.TrimSpace(req.OIDCConnectIssuerURL)
+ req.OIDCConnectDiscoveryURL = strings.TrimSpace(req.OIDCConnectDiscoveryURL)
+ req.OIDCConnectAuthorizeURL = strings.TrimSpace(req.OIDCConnectAuthorizeURL)
+ req.OIDCConnectTokenURL = strings.TrimSpace(req.OIDCConnectTokenURL)
+ req.OIDCConnectUserInfoURL = strings.TrimSpace(req.OIDCConnectUserInfoURL)
+ req.OIDCConnectJWKSURL = strings.TrimSpace(req.OIDCConnectJWKSURL)
+ req.OIDCConnectScopes = strings.TrimSpace(req.OIDCConnectScopes)
+ req.OIDCConnectRedirectURL = strings.TrimSpace(req.OIDCConnectRedirectURL)
+ req.OIDCConnectFrontendRedirectURL = strings.TrimSpace(req.OIDCConnectFrontendRedirectURL)
+ req.OIDCConnectTokenAuthMethod = strings.ToLower(strings.TrimSpace(req.OIDCConnectTokenAuthMethod))
+ req.OIDCConnectAllowedSigningAlgs = strings.TrimSpace(req.OIDCConnectAllowedSigningAlgs)
+ req.OIDCConnectUserInfoEmailPath = strings.TrimSpace(req.OIDCConnectUserInfoEmailPath)
+ req.OIDCConnectUserInfoIDPath = strings.TrimSpace(req.OIDCConnectUserInfoIDPath)
+ req.OIDCConnectUserInfoUsernamePath = strings.TrimSpace(req.OIDCConnectUserInfoUsernamePath)
+
+ if req.OIDCConnectProviderName == "" {
+ req.OIDCConnectProviderName = "OIDC"
+ }
+ if req.OIDCConnectClientID == "" {
+ response.BadRequest(c, "OIDC Client ID is required when enabled")
+ return
+ }
+ if req.OIDCConnectIssuerURL == "" {
+ response.BadRequest(c, "OIDC Issuer URL is required when enabled")
+ return
+ }
+ if err := config.ValidateAbsoluteHTTPURL(req.OIDCConnectIssuerURL); err != nil {
+ response.BadRequest(c, "OIDC Issuer URL must be an absolute http(s) URL")
+ return
+ }
+ if req.OIDCConnectDiscoveryURL != "" {
+ if err := config.ValidateAbsoluteHTTPURL(req.OIDCConnectDiscoveryURL); err != nil {
+ response.BadRequest(c, "OIDC Discovery URL must be an absolute http(s) URL")
+ return
+ }
+ }
+ if req.OIDCConnectAuthorizeURL != "" {
+ if err := config.ValidateAbsoluteHTTPURL(req.OIDCConnectAuthorizeURL); err != nil {
+ response.BadRequest(c, "OIDC Authorize URL must be an absolute http(s) URL")
+ return
+ }
+ }
+ if req.OIDCConnectTokenURL != "" {
+ if err := config.ValidateAbsoluteHTTPURL(req.OIDCConnectTokenURL); err != nil {
+ response.BadRequest(c, "OIDC Token URL must be an absolute http(s) URL")
+ return
+ }
+ }
+ if req.OIDCConnectUserInfoURL != "" {
+ if err := config.ValidateAbsoluteHTTPURL(req.OIDCConnectUserInfoURL); err != nil {
+ response.BadRequest(c, "OIDC UserInfo URL must be an absolute http(s) URL")
+ return
+ }
+ }
+ if req.OIDCConnectRedirectURL == "" {
+ response.BadRequest(c, "OIDC Redirect URL is required when enabled")
+ return
+ }
+ if err := config.ValidateAbsoluteHTTPURL(req.OIDCConnectRedirectURL); err != nil {
+ response.BadRequest(c, "OIDC Redirect URL must be an absolute http(s) URL")
+ return
+ }
+ if req.OIDCConnectFrontendRedirectURL == "" {
+ response.BadRequest(c, "OIDC Frontend Redirect URL is required when enabled")
+ return
+ }
+ if err := config.ValidateFrontendRedirectURL(req.OIDCConnectFrontendRedirectURL); err != nil {
+ response.BadRequest(c, "OIDC Frontend Redirect URL is invalid")
+ return
+ }
+ if !scopesContainOpenID(req.OIDCConnectScopes) {
+ response.BadRequest(c, "OIDC scopes must contain openid")
+ return
+ }
+ switch req.OIDCConnectTokenAuthMethod {
+ case "", "client_secret_post", "client_secret_basic", "none":
+ default:
+ response.BadRequest(c, "OIDC Token Auth Method must be one of client_secret_post/client_secret_basic/none")
+ return
+ }
+ if req.OIDCConnectTokenAuthMethod == "none" && !req.OIDCConnectUsePKCE {
+ response.BadRequest(c, "OIDC PKCE must be enabled when token_auth_method=none")
+ return
+ }
+ if req.OIDCConnectClockSkewSeconds < 0 || req.OIDCConnectClockSkewSeconds > 600 {
+ response.BadRequest(c, "OIDC clock skew seconds must be between 0 and 600")
+ return
+ }
+ if req.OIDCConnectValidateIDToken {
+ if req.OIDCConnectAllowedSigningAlgs == "" {
+ response.BadRequest(c, "OIDC Allowed Signing Algs is required when validate_id_token=true")
+ return
+ }
+ }
+ if req.OIDCConnectJWKSURL != "" {
+ if err := config.ValidateAbsoluteHTTPURL(req.OIDCConnectJWKSURL); err != nil {
+ response.BadRequest(c, "OIDC JWKS URL must be an absolute http(s) URL")
+ return
+ }
+ }
+ if req.OIDCConnectTokenAuthMethod == "" || req.OIDCConnectTokenAuthMethod == "client_secret_post" || req.OIDCConnectTokenAuthMethod == "client_secret_basic" {
+ if req.OIDCConnectClientSecret == "" {
+ if previousSettings.OIDCConnectClientSecret == "" {
+ response.BadRequest(c, "OIDC Client Secret is required when enabled")
+ return
+ }
+ req.OIDCConnectClientSecret = previousSettings.OIDCConnectClientSecret
+ }
+ }
+ }
+
// “购买订阅”页面配置验证
purchaseEnabled := previousSettings.PurchaseSubscriptionEnabled
if req.PurchaseSubscriptionEnabled != nil {
@@ -554,6 +789,28 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
LinuxDoConnectClientID: req.LinuxDoConnectClientID,
LinuxDoConnectClientSecret: req.LinuxDoConnectClientSecret,
LinuxDoConnectRedirectURL: req.LinuxDoConnectRedirectURL,
+ OIDCConnectEnabled: req.OIDCConnectEnabled,
+ OIDCConnectProviderName: req.OIDCConnectProviderName,
+ OIDCConnectClientID: req.OIDCConnectClientID,
+ OIDCConnectClientSecret: req.OIDCConnectClientSecret,
+ OIDCConnectIssuerURL: req.OIDCConnectIssuerURL,
+ OIDCConnectDiscoveryURL: req.OIDCConnectDiscoveryURL,
+ OIDCConnectAuthorizeURL: req.OIDCConnectAuthorizeURL,
+ OIDCConnectTokenURL: req.OIDCConnectTokenURL,
+ OIDCConnectUserInfoURL: req.OIDCConnectUserInfoURL,
+ OIDCConnectJWKSURL: req.OIDCConnectJWKSURL,
+ OIDCConnectScopes: req.OIDCConnectScopes,
+ OIDCConnectRedirectURL: req.OIDCConnectRedirectURL,
+ OIDCConnectFrontendRedirectURL: req.OIDCConnectFrontendRedirectURL,
+ OIDCConnectTokenAuthMethod: req.OIDCConnectTokenAuthMethod,
+ OIDCConnectUsePKCE: req.OIDCConnectUsePKCE,
+ OIDCConnectValidateIDToken: req.OIDCConnectValidateIDToken,
+ OIDCConnectAllowedSigningAlgs: req.OIDCConnectAllowedSigningAlgs,
+ OIDCConnectClockSkewSeconds: req.OIDCConnectClockSkewSeconds,
+ OIDCConnectRequireEmailVerified: req.OIDCConnectRequireEmailVerified,
+ OIDCConnectUserInfoEmailPath: req.OIDCConnectUserInfoEmailPath,
+ OIDCConnectUserInfoIDPath: req.OIDCConnectUserInfoIDPath,
+ OIDCConnectUserInfoUsernamePath: req.OIDCConnectUserInfoUsernamePath,
SiteName: req.SiteName,
SiteLogo: req.SiteLogo,
SiteSubtitle: req.SiteSubtitle,
@@ -564,6 +821,8 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
HideCcsImportButton: req.HideCcsImportButton,
PurchaseSubscriptionEnabled: purchaseEnabled,
PurchaseSubscriptionURL: purchaseURL,
+ TableDefaultPageSize: req.TableDefaultPageSize,
+ TablePageSizeOptions: req.TablePageSizeOptions,
CustomMenuItems: customMenuJSON,
CustomEndpoints: customEndpointsJSON,
DefaultConcurrency: req.DefaultConcurrency,
@@ -629,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)
// 重新获取设置返回
@@ -645,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,
@@ -669,6 +970,28 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
LinuxDoConnectClientID: updatedSettings.LinuxDoConnectClientID,
LinuxDoConnectClientSecretConfigured: updatedSettings.LinuxDoConnectClientSecretConfigured,
LinuxDoConnectRedirectURL: updatedSettings.LinuxDoConnectRedirectURL,
+ OIDCConnectEnabled: updatedSettings.OIDCConnectEnabled,
+ OIDCConnectProviderName: updatedSettings.OIDCConnectProviderName,
+ OIDCConnectClientID: updatedSettings.OIDCConnectClientID,
+ OIDCConnectClientSecretConfigured: updatedSettings.OIDCConnectClientSecretConfigured,
+ OIDCConnectIssuerURL: updatedSettings.OIDCConnectIssuerURL,
+ OIDCConnectDiscoveryURL: updatedSettings.OIDCConnectDiscoveryURL,
+ OIDCConnectAuthorizeURL: updatedSettings.OIDCConnectAuthorizeURL,
+ OIDCConnectTokenURL: updatedSettings.OIDCConnectTokenURL,
+ OIDCConnectUserInfoURL: updatedSettings.OIDCConnectUserInfoURL,
+ OIDCConnectJWKSURL: updatedSettings.OIDCConnectJWKSURL,
+ OIDCConnectScopes: updatedSettings.OIDCConnectScopes,
+ OIDCConnectRedirectURL: updatedSettings.OIDCConnectRedirectURL,
+ OIDCConnectFrontendRedirectURL: updatedSettings.OIDCConnectFrontendRedirectURL,
+ OIDCConnectTokenAuthMethod: updatedSettings.OIDCConnectTokenAuthMethod,
+ OIDCConnectUsePKCE: updatedSettings.OIDCConnectUsePKCE,
+ OIDCConnectValidateIDToken: updatedSettings.OIDCConnectValidateIDToken,
+ OIDCConnectAllowedSigningAlgs: updatedSettings.OIDCConnectAllowedSigningAlgs,
+ OIDCConnectClockSkewSeconds: updatedSettings.OIDCConnectClockSkewSeconds,
+ OIDCConnectRequireEmailVerified: updatedSettings.OIDCConnectRequireEmailVerified,
+ OIDCConnectUserInfoEmailPath: updatedSettings.OIDCConnectUserInfoEmailPath,
+ OIDCConnectUserInfoIDPath: updatedSettings.OIDCConnectUserInfoIDPath,
+ OIDCConnectUserInfoUsernamePath: updatedSettings.OIDCConnectUserInfoUsernamePath,
SiteName: updatedSettings.SiteName,
SiteLogo: updatedSettings.SiteLogo,
SiteSubtitle: updatedSettings.SiteSubtitle,
@@ -679,6 +1002,8 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
HideCcsImportButton: updatedSettings.HideCcsImportButton,
PurchaseSubscriptionEnabled: updatedSettings.PurchaseSubscriptionEnabled,
PurchaseSubscriptionURL: updatedSettings.PurchaseSubscriptionURL,
+ TableDefaultPageSize: updatedSettings.TableDefaultPageSize,
+ TablePageSizeOptions: updatedSettings.TablePageSizeOptions,
CustomMenuItems: dto.ParseCustomMenuItems(updatedSettings.CustomMenuItems),
CustomEndpoints: dto.ParseCustomEndpoints(updatedSettings.CustomEndpoints),
DefaultConcurrency: updatedSettings.DefaultConcurrency,
@@ -702,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
@@ -787,6 +1143,72 @@ func diffSettings(before *service.SystemSettings, after *service.SystemSettings,
if before.LinuxDoConnectRedirectURL != after.LinuxDoConnectRedirectURL {
changed = append(changed, "linuxdo_connect_redirect_url")
}
+ if before.OIDCConnectEnabled != after.OIDCConnectEnabled {
+ changed = append(changed, "oidc_connect_enabled")
+ }
+ if before.OIDCConnectProviderName != after.OIDCConnectProviderName {
+ changed = append(changed, "oidc_connect_provider_name")
+ }
+ if before.OIDCConnectClientID != after.OIDCConnectClientID {
+ changed = append(changed, "oidc_connect_client_id")
+ }
+ if req.OIDCConnectClientSecret != "" {
+ changed = append(changed, "oidc_connect_client_secret")
+ }
+ if before.OIDCConnectIssuerURL != after.OIDCConnectIssuerURL {
+ changed = append(changed, "oidc_connect_issuer_url")
+ }
+ if before.OIDCConnectDiscoveryURL != after.OIDCConnectDiscoveryURL {
+ changed = append(changed, "oidc_connect_discovery_url")
+ }
+ if before.OIDCConnectAuthorizeURL != after.OIDCConnectAuthorizeURL {
+ changed = append(changed, "oidc_connect_authorize_url")
+ }
+ if before.OIDCConnectTokenURL != after.OIDCConnectTokenURL {
+ changed = append(changed, "oidc_connect_token_url")
+ }
+ if before.OIDCConnectUserInfoURL != after.OIDCConnectUserInfoURL {
+ changed = append(changed, "oidc_connect_userinfo_url")
+ }
+ if before.OIDCConnectJWKSURL != after.OIDCConnectJWKSURL {
+ changed = append(changed, "oidc_connect_jwks_url")
+ }
+ if before.OIDCConnectScopes != after.OIDCConnectScopes {
+ changed = append(changed, "oidc_connect_scopes")
+ }
+ if before.OIDCConnectRedirectURL != after.OIDCConnectRedirectURL {
+ changed = append(changed, "oidc_connect_redirect_url")
+ }
+ if before.OIDCConnectFrontendRedirectURL != after.OIDCConnectFrontendRedirectURL {
+ changed = append(changed, "oidc_connect_frontend_redirect_url")
+ }
+ if before.OIDCConnectTokenAuthMethod != after.OIDCConnectTokenAuthMethod {
+ changed = append(changed, "oidc_connect_token_auth_method")
+ }
+ if before.OIDCConnectUsePKCE != after.OIDCConnectUsePKCE {
+ changed = append(changed, "oidc_connect_use_pkce")
+ }
+ if before.OIDCConnectValidateIDToken != after.OIDCConnectValidateIDToken {
+ changed = append(changed, "oidc_connect_validate_id_token")
+ }
+ if before.OIDCConnectAllowedSigningAlgs != after.OIDCConnectAllowedSigningAlgs {
+ changed = append(changed, "oidc_connect_allowed_signing_algs")
+ }
+ if before.OIDCConnectClockSkewSeconds != after.OIDCConnectClockSkewSeconds {
+ changed = append(changed, "oidc_connect_clock_skew_seconds")
+ }
+ if before.OIDCConnectRequireEmailVerified != after.OIDCConnectRequireEmailVerified {
+ changed = append(changed, "oidc_connect_require_email_verified")
+ }
+ if before.OIDCConnectUserInfoEmailPath != after.OIDCConnectUserInfoEmailPath {
+ changed = append(changed, "oidc_connect_userinfo_email_path")
+ }
+ if before.OIDCConnectUserInfoIDPath != after.OIDCConnectUserInfoIDPath {
+ changed = append(changed, "oidc_connect_userinfo_id_path")
+ }
+ if before.OIDCConnectUserInfoUsernamePath != after.OIDCConnectUserInfoUsernamePath {
+ changed = append(changed, "oidc_connect_userinfo_username_path")
+ }
if before.SiteName != after.SiteName {
changed = append(changed, "site_name")
}
@@ -871,6 +1293,12 @@ func diffSettings(before *service.SystemSettings, after *service.SystemSettings,
if before.PurchaseSubscriptionURL != after.PurchaseSubscriptionURL {
changed = append(changed, "purchase_subscription_url")
}
+ if before.TableDefaultPageSize != after.TableDefaultPageSize {
+ changed = append(changed, "table_default_page_size")
+ }
+ if !equalIntSlice(before.TablePageSizeOptions, after.TablePageSizeOptions) {
+ changed = append(changed, "table_page_size_options")
+ }
if before.CustomMenuItems != after.CustomMenuItems {
changed = append(changed, "custom_menu_items")
}
@@ -927,6 +1355,18 @@ func equalDefaultSubscriptions(a, b []service.DefaultSubscriptionSetting) bool {
return true
}
+func equalIntSlice(a, b []int) bool {
+ if len(a) != len(b) {
+ return false
+ }
+ for i := range a {
+ if a[i] != b[i] {
+ return false
+ }
+ }
+ return true
+}
+
// TestSMTPRequest 测试SMTP连接请求
type TestSMTPRequest struct {
SMTPHost string `json:"smtp_host"`
diff --git a/backend/internal/handler/admin/usage_handler.go b/backend/internal/handler/admin/usage_handler.go
index 2967b3840e8c5c29d714780d4762ce356cf051a5..0857a13880b351ab9409b30bd50eeabc5376b884 100644
--- a/backend/internal/handler/admin/usage_handler.go
+++ b/backend/internal/handler/admin/usage_handler.go
@@ -165,7 +165,12 @@ func (h *UsageHandler) List(c *gin.Context) {
endTime = &t
}
- params := pagination.PaginationParams{Page: page, PageSize: pageSize}
+ params := pagination.PaginationParams{
+ Page: page,
+ PageSize: pageSize,
+ SortBy: c.DefaultQuery("sort_by", "created_at"),
+ SortOrder: c.DefaultQuery("sort_order", "desc"),
+ }
filters := usagestats.UsageLogFilters{
UserID: userID,
APIKeyID: apiKeyID,
@@ -339,7 +344,7 @@ func (h *UsageHandler) SearchUsers(c *gin.Context) {
}
// Limit to 30 results
- users, _, err := h.adminService.ListUsers(c.Request.Context(), 1, 30, service.UserListFilters{Search: keyword})
+ users, _, err := h.adminService.ListUsers(c.Request.Context(), 1, 30, service.UserListFilters{Search: keyword}, "email", "asc")
if err != nil {
response.ErrorFrom(c, err)
return
diff --git a/backend/internal/handler/admin/usage_handler_request_type_test.go b/backend/internal/handler/admin/usage_handler_request_type_test.go
index 3f158316fecd03b2c93b098750d67a4ad997b44f..882cbe9362b2259006f405a84a9ae504f8272716 100644
--- a/backend/internal/handler/admin/usage_handler_request_type_test.go
+++ b/backend/internal/handler/admin/usage_handler_request_type_test.go
@@ -15,11 +15,13 @@ import (
type adminUsageRepoCapture struct {
service.UsageLogRepository
+ listParams pagination.PaginationParams
listFilters usagestats.UsageLogFilters
statsFilters usagestats.UsageLogFilters
}
func (s *adminUsageRepoCapture) ListWithFilters(ctx context.Context, params pagination.PaginationParams, filters usagestats.UsageLogFilters) ([]service.UsageLog, *pagination.PaginationResult, error) {
+ s.listParams = params
s.listFilters = filters
return []service.UsageLog{}, &pagination.PaginationResult{
Total: 0,
diff --git a/backend/internal/handler/admin/usage_handler_sort_test.go b/backend/internal/handler/admin/usage_handler_sort_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..dac826762a046df77d5ae20e6a386bde0804545c
--- /dev/null
+++ b/backend/internal/handler/admin/usage_handler_sort_test.go
@@ -0,0 +1,35 @@
+package admin
+
+import (
+ "net/http"
+ "net/http/httptest"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+func TestAdminUsageListSortParams(t *testing.T) {
+ repo := &adminUsageRepoCapture{}
+ router := newAdminUsageRequestTypeTestRouter(repo)
+
+ req := httptest.NewRequest(http.MethodGet, "/admin/usage?sort_by=model&sort_order=ASC", nil)
+ rec := httptest.NewRecorder()
+ router.ServeHTTP(rec, req)
+
+ require.Equal(t, http.StatusOK, rec.Code)
+ require.Equal(t, "model", repo.listParams.SortBy)
+ require.Equal(t, "ASC", repo.listParams.SortOrder)
+}
+
+func TestAdminUsageListSortDefaults(t *testing.T) {
+ repo := &adminUsageRepoCapture{}
+ router := newAdminUsageRequestTypeTestRouter(repo)
+
+ req := httptest.NewRequest(http.MethodGet, "/admin/usage", nil)
+ rec := httptest.NewRecorder()
+ router.ServeHTTP(rec, req)
+
+ require.Equal(t, http.StatusOK, rec.Code)
+ require.Equal(t, "created_at", repo.listParams.SortBy)
+ require.Equal(t, "desc", repo.listParams.SortOrder)
+}
diff --git a/backend/internal/handler/admin/user_handler.go b/backend/internal/handler/admin/user_handler.go
index a357657e20534f78c254c5d6c2860e0bfebadd67..1453bd0739ddf6a518e010db4f3256fcdc8692fe 100644
--- a/backend/internal/handler/admin/user_handler.go
+++ b/backend/internal/handler/admin/user_handler.go
@@ -91,12 +91,14 @@ func (h *UserHandler) List(c *gin.Context) {
GroupName: strings.TrimSpace(c.Query("group_name")),
Attributes: parseAttributeFilters(c),
}
+ sortBy := c.DefaultQuery("sort_by", "created_at")
+ sortOrder := c.DefaultQuery("sort_order", "desc")
if raw, ok := c.GetQuery("include_subscriptions"); ok {
includeSubscriptions := parseBoolQueryWithDefault(raw, true)
filters.IncludeSubscriptions = &includeSubscriptions
}
- users, total, err := h.adminService.ListUsers(c.Request.Context(), page, pageSize, filters)
+ users, total, err := h.adminService.ListUsers(c.Request.Context(), page, pageSize, filters, sortBy, sortOrder)
if err != nil {
response.ErrorFrom(c, err)
return
@@ -290,8 +292,10 @@ func (h *UserHandler) GetUserAPIKeys(c *gin.Context) {
}
page, pageSize := response.ParsePagination(c)
+ sortBy := c.DefaultQuery("sort_by", "created_at")
+ sortOrder := c.DefaultQuery("sort_order", "desc")
- keys, total, err := h.adminService.GetUserAPIKeys(c.Request.Context(), userID, page, pageSize)
+ keys, total, err := h.adminService.GetUserAPIKeys(c.Request.Context(), userID, page, pageSize, sortBy, sortOrder)
if err != nil {
response.ErrorFrom(c, err)
return
diff --git a/backend/internal/handler/api_key_handler.go b/backend/internal/handler/api_key_handler.go
index 951aed08db73b0aa90abd6ecc239e4d728f565dc..9d6c6c15230d4c18ba468c0e3f38c944010eda6b 100644
--- a/backend/internal/handler/api_key_handler.go
+++ b/backend/internal/handler/api_key_handler.go
@@ -72,7 +72,12 @@ func (h *APIKeyHandler) List(c *gin.Context) {
}
page, pageSize := response.ParsePagination(c)
- params := pagination.PaginationParams{Page: page, PageSize: pageSize}
+ params := pagination.PaginationParams{
+ Page: page,
+ PageSize: pageSize,
+ SortBy: c.DefaultQuery("sort_by", "created_at"),
+ SortOrder: c.DefaultQuery("sort_order", "desc"),
+ }
// Parse filter parameters
var filters service.APIKeyListFilters
diff --git a/backend/internal/handler/auth_oidc_oauth.go b/backend/internal/handler/auth_oidc_oauth.go
new file mode 100644
index 0000000000000000000000000000000000000000..9d24df88ab1a08a2a436e120e7e367c65b66c881
--- /dev/null
+++ b/backend/internal/handler/auth_oidc_oauth.go
@@ -0,0 +1,873 @@
+package handler
+
+import (
+ "context"
+ "crypto/ecdsa"
+ "crypto/elliptic"
+ "crypto/rsa"
+ "crypto/sha256"
+ "encoding/base64"
+ "encoding/hex"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "log"
+ "math/big"
+ "net/http"
+ "net/url"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/Wei-Shaw/sub2api/internal/config"
+ infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
+ "github.com/Wei-Shaw/sub2api/internal/pkg/oauth"
+ "github.com/Wei-Shaw/sub2api/internal/pkg/response"
+ "github.com/Wei-Shaw/sub2api/internal/service"
+
+ "github.com/gin-gonic/gin"
+ "github.com/golang-jwt/jwt/v5"
+ "github.com/imroc/req/v3"
+ "github.com/tidwall/gjson"
+)
+
+const (
+ oidcOAuthCookiePath = "/api/v1/auth/oauth/oidc"
+ oidcOAuthStateCookieName = "oidc_oauth_state"
+ oidcOAuthVerifierCookie = "oidc_oauth_verifier"
+ oidcOAuthRedirectCookie = "oidc_oauth_redirect"
+ oidcOAuthNonceCookie = "oidc_oauth_nonce"
+ oidcOAuthCookieMaxAgeSec = 10 * 60 // 10 minutes
+ oidcOAuthDefaultRedirectTo = "/dashboard"
+ oidcOAuthDefaultFrontendCB = "/auth/oidc/callback"
+)
+
+type oidcTokenResponse struct {
+ AccessToken string `json:"access_token"`
+ TokenType string `json:"token_type"`
+ ExpiresIn int64 `json:"expires_in"`
+ RefreshToken string `json:"refresh_token,omitempty"`
+ Scope string `json:"scope,omitempty"`
+ IDToken string `json:"id_token,omitempty"`
+}
+
+type oidcTokenExchangeError struct {
+ StatusCode int
+ ProviderError string
+ ProviderDescription string
+ Body string
+}
+
+func (e *oidcTokenExchangeError) Error() string {
+ if e == nil {
+ return ""
+ }
+ parts := []string{fmt.Sprintf("token exchange status=%d", e.StatusCode)}
+ if strings.TrimSpace(e.ProviderError) != "" {
+ parts = append(parts, "error="+strings.TrimSpace(e.ProviderError))
+ }
+ if strings.TrimSpace(e.ProviderDescription) != "" {
+ parts = append(parts, "error_description="+strings.TrimSpace(e.ProviderDescription))
+ }
+ return strings.Join(parts, " ")
+}
+
+type oidcIDTokenClaims struct {
+ Email string `json:"email,omitempty"`
+ EmailVerified *bool `json:"email_verified,omitempty"`
+ PreferredUsername string `json:"preferred_username,omitempty"`
+ Name string `json:"name,omitempty"`
+ Nonce string `json:"nonce,omitempty"`
+ Azp string `json:"azp,omitempty"`
+ jwt.RegisteredClaims
+}
+
+type oidcUserInfoClaims struct {
+ Email string
+ Username string
+ Subject string
+ EmailVerified *bool
+}
+
+type oidcJWKSet struct {
+ Keys []oidcJWK `json:"keys"`
+}
+
+type oidcJWK struct {
+ Kty string `json:"kty"`
+ Kid string `json:"kid"`
+ Use string `json:"use"`
+ Alg string `json:"alg"`
+
+ N string `json:"n"`
+ E string `json:"e"`
+
+ Crv string `json:"crv"`
+ X string `json:"x"`
+ Y string `json:"y"`
+}
+
+// OIDCOAuthStart 启动通用 OIDC OAuth 登录流程。
+// GET /api/v1/auth/oauth/oidc/start?redirect=/dashboard
+func (h *AuthHandler) OIDCOAuthStart(c *gin.Context) {
+ cfg, err := h.getOIDCOAuthConfig(c.Request.Context())
+ if err != nil {
+ response.ErrorFrom(c, err)
+ return
+ }
+
+ state, err := oauth.GenerateState()
+ if err != nil {
+ response.ErrorFrom(c, infraerrors.InternalServer("OAUTH_STATE_GEN_FAILED", "failed to generate oauth state").WithCause(err))
+ return
+ }
+
+ redirectTo := sanitizeFrontendRedirectPath(c.Query("redirect"))
+ if redirectTo == "" {
+ redirectTo = oidcOAuthDefaultRedirectTo
+ }
+
+ secureCookie := isRequestHTTPS(c)
+ oidcSetCookie(c, oidcOAuthStateCookieName, encodeCookieValue(state), oidcOAuthCookieMaxAgeSec, secureCookie)
+ oidcSetCookie(c, oidcOAuthRedirectCookie, encodeCookieValue(redirectTo), oidcOAuthCookieMaxAgeSec, secureCookie)
+
+ codeChallenge := ""
+ if cfg.UsePKCE {
+ verifier, genErr := oauth.GenerateCodeVerifier()
+ if genErr != nil {
+ response.ErrorFrom(c, infraerrors.InternalServer("OAUTH_PKCE_GEN_FAILED", "failed to generate pkce verifier").WithCause(genErr))
+ return
+ }
+ codeChallenge = oauth.GenerateCodeChallenge(verifier)
+ oidcSetCookie(c, oidcOAuthVerifierCookie, encodeCookieValue(verifier), oidcOAuthCookieMaxAgeSec, secureCookie)
+ }
+
+ nonce := ""
+ if cfg.ValidateIDToken {
+ nonce, err = oauth.GenerateState()
+ if err != nil {
+ response.ErrorFrom(c, infraerrors.InternalServer("OAUTH_NONCE_GEN_FAILED", "failed to generate oauth nonce").WithCause(err))
+ return
+ }
+ oidcSetCookie(c, oidcOAuthNonceCookie, encodeCookieValue(nonce), oidcOAuthCookieMaxAgeSec, secureCookie)
+ }
+
+ redirectURI := strings.TrimSpace(cfg.RedirectURL)
+ if redirectURI == "" {
+ response.ErrorFrom(c, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "oauth redirect url not configured"))
+ return
+ }
+
+ authURL, err := buildOIDCAuthorizeURL(cfg, state, nonce, codeChallenge, redirectURI)
+ if err != nil {
+ response.ErrorFrom(c, infraerrors.InternalServer("OAUTH_BUILD_URL_FAILED", "failed to build oauth authorization url").WithCause(err))
+ return
+ }
+
+ c.Redirect(http.StatusFound, authURL)
+}
+
+// OIDCOAuthCallback 处理 OIDC 回调:校验 id_token、创建/登录用户并重定向到前端。
+// GET /api/v1/auth/oauth/oidc/callback?code=...&state=...
+func (h *AuthHandler) OIDCOAuthCallback(c *gin.Context) {
+ cfg, cfgErr := h.getOIDCOAuthConfig(c.Request.Context())
+ if cfgErr != nil {
+ response.ErrorFrom(c, cfgErr)
+ return
+ }
+
+ frontendCallback := strings.TrimSpace(cfg.FrontendRedirectURL)
+ if frontendCallback == "" {
+ frontendCallback = oidcOAuthDefaultFrontendCB
+ }
+
+ if providerErr := strings.TrimSpace(c.Query("error")); providerErr != "" {
+ redirectOAuthError(c, frontendCallback, "provider_error", providerErr, c.Query("error_description"))
+ return
+ }
+
+ code := strings.TrimSpace(c.Query("code"))
+ state := strings.TrimSpace(c.Query("state"))
+ if code == "" || state == "" {
+ redirectOAuthError(c, frontendCallback, "missing_params", "missing code/state", "")
+ return
+ }
+
+ secureCookie := isRequestHTTPS(c)
+ defer func() {
+ oidcClearCookie(c, oidcOAuthStateCookieName, secureCookie)
+ oidcClearCookie(c, oidcOAuthVerifierCookie, secureCookie)
+ oidcClearCookie(c, oidcOAuthRedirectCookie, secureCookie)
+ oidcClearCookie(c, oidcOAuthNonceCookie, secureCookie)
+ }()
+
+ expectedState, err := readCookieDecoded(c, oidcOAuthStateCookieName)
+ if err != nil || expectedState == "" || state != expectedState {
+ redirectOAuthError(c, frontendCallback, "invalid_state", "invalid oauth state", "")
+ return
+ }
+
+ redirectTo, _ := readCookieDecoded(c, oidcOAuthRedirectCookie)
+ redirectTo = sanitizeFrontendRedirectPath(redirectTo)
+ if redirectTo == "" {
+ redirectTo = oidcOAuthDefaultRedirectTo
+ }
+
+ codeVerifier := ""
+ if cfg.UsePKCE {
+ codeVerifier, _ = readCookieDecoded(c, oidcOAuthVerifierCookie)
+ if codeVerifier == "" {
+ redirectOAuthError(c, frontendCallback, "missing_verifier", "missing pkce verifier", "")
+ return
+ }
+ }
+
+ expectedNonce := ""
+ if cfg.ValidateIDToken {
+ expectedNonce, _ = readCookieDecoded(c, oidcOAuthNonceCookie)
+ if expectedNonce == "" {
+ redirectOAuthError(c, frontendCallback, "missing_nonce", "missing oauth nonce", "")
+ return
+ }
+ }
+
+ redirectURI := strings.TrimSpace(cfg.RedirectURL)
+ if redirectURI == "" {
+ redirectOAuthError(c, frontendCallback, "config_error", "oauth redirect url not configured", "")
+ return
+ }
+
+ tokenResp, err := oidcExchangeCode(c.Request.Context(), cfg, code, redirectURI, codeVerifier)
+ if err != nil {
+ description := ""
+ var exchangeErr *oidcTokenExchangeError
+ if errors.As(err, &exchangeErr) && exchangeErr != nil {
+ log.Printf(
+ "[OIDC OAuth] token exchange failed: status=%d provider_error=%q provider_description=%q body=%s",
+ exchangeErr.StatusCode,
+ exchangeErr.ProviderError,
+ exchangeErr.ProviderDescription,
+ truncateLogValue(exchangeErr.Body, 2048),
+ )
+ description = exchangeErr.Error()
+ } else {
+ log.Printf("[OIDC OAuth] token exchange failed: %v", err)
+ description = err.Error()
+ }
+ redirectOAuthError(c, frontendCallback, "token_exchange_failed", "failed to exchange oauth code", singleLine(description))
+ return
+ }
+
+ if cfg.ValidateIDToken && strings.TrimSpace(tokenResp.IDToken) == "" {
+ redirectOAuthError(c, frontendCallback, "missing_id_token", "missing id_token", "")
+ return
+ }
+
+ idClaims, err := oidcParseAndValidateIDToken(c.Request.Context(), cfg, tokenResp.IDToken, expectedNonce)
+ if err != nil {
+ log.Printf("[OIDC OAuth] id_token validation failed: %v", err)
+ redirectOAuthError(c, frontendCallback, "invalid_id_token", "failed to validate id_token", "")
+ return
+ }
+
+ userInfoClaims, err := oidcFetchUserInfo(c.Request.Context(), cfg, tokenResp)
+ if err != nil {
+ log.Printf("[OIDC OAuth] userinfo fetch failed: %v", err)
+ redirectOAuthError(c, frontendCallback, "userinfo_failed", "failed to fetch user info", "")
+ return
+ }
+
+ subject := strings.TrimSpace(idClaims.Subject)
+ if subject == "" {
+ subject = strings.TrimSpace(userInfoClaims.Subject)
+ }
+ if subject == "" {
+ redirectOAuthError(c, frontendCallback, "missing_subject", "missing subject claim", "")
+ return
+ }
+ issuer := strings.TrimSpace(idClaims.Issuer)
+ if issuer == "" {
+ issuer = strings.TrimSpace(cfg.IssuerURL)
+ }
+ if issuer == "" {
+ redirectOAuthError(c, frontendCallback, "missing_issuer", "missing issuer claim", "")
+ return
+ }
+
+ emailVerified := userInfoClaims.EmailVerified
+ if emailVerified == nil {
+ emailVerified = idClaims.EmailVerified
+ }
+ if cfg.RequireEmailVerified {
+ if emailVerified == nil || !*emailVerified {
+ redirectOAuthError(c, frontendCallback, "email_not_verified", "email is not verified", "")
+ return
+ }
+ }
+
+ identityKey := oidcIdentityKey(issuer, subject)
+ email := oidcSelectLoginEmail(userInfoClaims.Email, idClaims.Email, identityKey)
+ username := firstNonEmpty(
+ userInfoClaims.Username,
+ idClaims.PreferredUsername,
+ idClaims.Name,
+ oidcFallbackUsername(subject),
+ )
+
+ // 传入空邀请码;如果需要邀请码,服务层返回 ErrOAuthInvitationRequired
+ tokenPair, _, err := h.authService.LoginOrRegisterOAuthWithTokenPair(c.Request.Context(), email, username, "")
+ if err != nil {
+ if errors.Is(err, service.ErrOAuthInvitationRequired) {
+ pendingToken, tokenErr := h.authService.CreatePendingOAuthToken(email, username)
+ if tokenErr != nil {
+ redirectOAuthError(c, frontendCallback, "login_failed", "service_error", "")
+ return
+ }
+ fragment := url.Values{}
+ fragment.Set("error", "invitation_required")
+ fragment.Set("pending_oauth_token", pendingToken)
+ fragment.Set("redirect", redirectTo)
+ redirectWithFragment(c, frontendCallback, fragment)
+ return
+ }
+ redirectOAuthError(c, frontendCallback, "login_failed", infraerrors.Reason(err), infraerrors.Message(err))
+ return
+ }
+
+ fragment := url.Values{}
+ fragment.Set("access_token", tokenPair.AccessToken)
+ fragment.Set("refresh_token", tokenPair.RefreshToken)
+ fragment.Set("expires_in", fmt.Sprintf("%d", tokenPair.ExpiresIn))
+ fragment.Set("token_type", "Bearer")
+ fragment.Set("redirect", redirectTo)
+ redirectWithFragment(c, frontendCallback, fragment)
+}
+
+type completeOIDCOAuthRequest struct {
+ PendingOAuthToken string `json:"pending_oauth_token" binding:"required"`
+ InvitationCode string `json:"invitation_code" binding:"required"`
+}
+
+// CompleteOIDCOAuthRegistration completes a pending OAuth registration by validating
+// the invitation code and creating the user account.
+// POST /api/v1/auth/oauth/oidc/complete-registration
+func (h *AuthHandler) CompleteOIDCOAuthRegistration(c *gin.Context) {
+ var req completeOIDCOAuthRequest
+ if err := c.ShouldBindJSON(&req); err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "INVALID_REQUEST", "message": err.Error()})
+ return
+ }
+
+ email, username, err := h.authService.VerifyPendingOAuthToken(req.PendingOAuthToken)
+ if err != nil {
+ c.JSON(http.StatusUnauthorized, gin.H{"error": "INVALID_TOKEN", "message": "invalid or expired registration token"})
+ return
+ }
+
+ tokenPair, _, err := h.authService.LoginOrRegisterOAuthWithTokenPair(c.Request.Context(), email, username, req.InvitationCode)
+ if err != nil {
+ response.ErrorFrom(c, err)
+ return
+ }
+
+ c.JSON(http.StatusOK, gin.H{
+ "access_token": tokenPair.AccessToken,
+ "refresh_token": tokenPair.RefreshToken,
+ "expires_in": tokenPair.ExpiresIn,
+ "token_type": "Bearer",
+ })
+}
+
+func (h *AuthHandler) getOIDCOAuthConfig(ctx context.Context) (config.OIDCConnectConfig, error) {
+ if h != nil && h.settingSvc != nil {
+ return h.settingSvc.GetOIDCConnectOAuthConfig(ctx)
+ }
+ if h == nil || h.cfg == nil {
+ return config.OIDCConnectConfig{}, infraerrors.ServiceUnavailable("CONFIG_NOT_READY", "config not loaded")
+ }
+ if !h.cfg.OIDC.Enabled {
+ return config.OIDCConnectConfig{}, infraerrors.NotFound("OAUTH_DISABLED", "oauth login is disabled")
+ }
+ return h.cfg.OIDC, nil
+}
+
+func oidcExchangeCode(
+ ctx context.Context,
+ cfg config.OIDCConnectConfig,
+ code string,
+ redirectURI string,
+ codeVerifier string,
+) (*oidcTokenResponse, error) {
+ client := req.C().SetTimeout(30 * time.Second)
+
+ form := url.Values{}
+ form.Set("grant_type", "authorization_code")
+ form.Set("client_id", cfg.ClientID)
+ form.Set("code", code)
+ form.Set("redirect_uri", redirectURI)
+ if cfg.UsePKCE {
+ form.Set("code_verifier", codeVerifier)
+ }
+
+ r := client.R().
+ SetContext(ctx).
+ SetHeader("Accept", "application/json")
+
+ switch strings.ToLower(strings.TrimSpace(cfg.TokenAuthMethod)) {
+ case "", "client_secret_post":
+ form.Set("client_secret", cfg.ClientSecret)
+ case "client_secret_basic":
+ r.SetBasicAuth(cfg.ClientID, cfg.ClientSecret)
+ case "none":
+ default:
+ return nil, fmt.Errorf("unsupported token_auth_method: %s", cfg.TokenAuthMethod)
+ }
+
+ resp, err := r.SetFormDataFromValues(form).Post(cfg.TokenURL)
+ if err != nil {
+ return nil, fmt.Errorf("request token: %w", err)
+ }
+ body := strings.TrimSpace(resp.String())
+ if !resp.IsSuccessState() {
+ providerErr, providerDesc := parseOAuthProviderError(body)
+ return nil, &oidcTokenExchangeError{
+ StatusCode: resp.StatusCode,
+ ProviderError: providerErr,
+ ProviderDescription: providerDesc,
+ Body: body,
+ }
+ }
+
+ tokenResp, ok := oidcParseTokenResponse(body)
+ if !ok {
+ return nil, &oidcTokenExchangeError{StatusCode: resp.StatusCode, Body: body}
+ }
+ if strings.TrimSpace(tokenResp.TokenType) == "" {
+ tokenResp.TokenType = "Bearer"
+ }
+ if strings.TrimSpace(tokenResp.AccessToken) == "" && strings.TrimSpace(tokenResp.IDToken) == "" {
+ return nil, &oidcTokenExchangeError{StatusCode: resp.StatusCode, Body: body}
+ }
+ return tokenResp, nil
+}
+
+func oidcParseTokenResponse(body string) (*oidcTokenResponse, bool) {
+ body = strings.TrimSpace(body)
+ if body == "" {
+ return nil, false
+ }
+
+ accessToken := strings.TrimSpace(getGJSON(body, "access_token"))
+ idToken := strings.TrimSpace(getGJSON(body, "id_token"))
+ if accessToken != "" || idToken != "" {
+ tokenType := strings.TrimSpace(getGJSON(body, "token_type"))
+ refreshToken := strings.TrimSpace(getGJSON(body, "refresh_token"))
+ scope := strings.TrimSpace(getGJSON(body, "scope"))
+ expiresIn := gjson.Get(body, "expires_in").Int()
+ return &oidcTokenResponse{
+ AccessToken: accessToken,
+ TokenType: tokenType,
+ ExpiresIn: expiresIn,
+ RefreshToken: refreshToken,
+ Scope: scope,
+ IDToken: idToken,
+ }, true
+ }
+
+ values, err := url.ParseQuery(body)
+ if err != nil {
+ return nil, false
+ }
+ accessToken = strings.TrimSpace(values.Get("access_token"))
+ idToken = strings.TrimSpace(values.Get("id_token"))
+ if accessToken == "" && idToken == "" {
+ return nil, false
+ }
+ expiresIn := int64(0)
+ if raw := strings.TrimSpace(values.Get("expires_in")); raw != "" {
+ if v, parseErr := strconv.ParseInt(raw, 10, 64); parseErr == nil {
+ expiresIn = v
+ }
+ }
+ return &oidcTokenResponse{
+ AccessToken: accessToken,
+ TokenType: strings.TrimSpace(values.Get("token_type")),
+ ExpiresIn: expiresIn,
+ RefreshToken: strings.TrimSpace(values.Get("refresh_token")),
+ Scope: strings.TrimSpace(values.Get("scope")),
+ IDToken: idToken,
+ }, true
+}
+
+func oidcFetchUserInfo(
+ ctx context.Context,
+ cfg config.OIDCConnectConfig,
+ token *oidcTokenResponse,
+) (*oidcUserInfoClaims, error) {
+ if strings.TrimSpace(cfg.UserInfoURL) == "" {
+ return &oidcUserInfoClaims{}, nil
+ }
+ if token == nil || strings.TrimSpace(token.AccessToken) == "" {
+ return nil, errors.New("missing access_token for userinfo request")
+ }
+
+ client := req.C().SetTimeout(30 * time.Second)
+ authorization, err := buildBearerAuthorization(token.TokenType, token.AccessToken)
+ if err != nil {
+ return nil, fmt.Errorf("invalid token for userinfo request: %w", err)
+ }
+
+ resp, err := client.R().
+ SetContext(ctx).
+ SetHeader("Accept", "application/json").
+ SetHeader("Authorization", authorization).
+ Get(cfg.UserInfoURL)
+ if err != nil {
+ return nil, fmt.Errorf("request userinfo: %w", err)
+ }
+ if !resp.IsSuccessState() {
+ return nil, fmt.Errorf("userinfo status=%d", resp.StatusCode)
+ }
+
+ return oidcParseUserInfo(resp.String(), cfg), nil
+}
+
+func oidcParseUserInfo(body string, cfg config.OIDCConnectConfig) *oidcUserInfoClaims {
+ claims := &oidcUserInfoClaims{}
+ claims.Email = firstNonEmpty(
+ getGJSON(body, cfg.UserInfoEmailPath),
+ getGJSON(body, "email"),
+ getGJSON(body, "user.email"),
+ getGJSON(body, "data.email"),
+ getGJSON(body, "attributes.email"),
+ )
+ claims.Username = firstNonEmpty(
+ getGJSON(body, cfg.UserInfoUsernamePath),
+ getGJSON(body, "preferred_username"),
+ getGJSON(body, "username"),
+ getGJSON(body, "name"),
+ getGJSON(body, "user.username"),
+ getGJSON(body, "user.name"),
+ )
+ claims.Subject = firstNonEmpty(
+ getGJSON(body, cfg.UserInfoIDPath),
+ getGJSON(body, "sub"),
+ getGJSON(body, "id"),
+ getGJSON(body, "user_id"),
+ getGJSON(body, "uid"),
+ getGJSON(body, "user.id"),
+ )
+ if verified, ok := getGJSONBool(body, "email_verified"); ok {
+ claims.EmailVerified = &verified
+ }
+ claims.Email = strings.TrimSpace(claims.Email)
+ claims.Username = strings.TrimSpace(claims.Username)
+ claims.Subject = strings.TrimSpace(claims.Subject)
+ return claims
+}
+
+func getGJSONBool(body string, path string) (bool, bool) {
+ path = strings.TrimSpace(path)
+ if path == "" {
+ return false, false
+ }
+ res := gjson.Get(body, path)
+ if !res.Exists() {
+ return false, false
+ }
+ return res.Bool(), true
+}
+
+func buildOIDCAuthorizeURL(cfg config.OIDCConnectConfig, state, nonce, codeChallenge, redirectURI string) (string, error) {
+ u, err := url.Parse(cfg.AuthorizeURL)
+ if err != nil {
+ return "", fmt.Errorf("parse authorize_url: %w", err)
+ }
+
+ q := u.Query()
+ q.Set("response_type", "code")
+ q.Set("client_id", cfg.ClientID)
+ q.Set("redirect_uri", redirectURI)
+ if strings.TrimSpace(cfg.Scopes) != "" {
+ q.Set("scope", cfg.Scopes)
+ }
+ q.Set("state", state)
+ if strings.TrimSpace(nonce) != "" {
+ q.Set("nonce", nonce)
+ }
+ if cfg.UsePKCE {
+ q.Set("code_challenge", codeChallenge)
+ q.Set("code_challenge_method", "S256")
+ }
+
+ u.RawQuery = q.Encode()
+ return u.String(), nil
+}
+
+func oidcParseAndValidateIDToken(ctx context.Context, cfg config.OIDCConnectConfig, idToken string, expectedNonce string) (*oidcIDTokenClaims, error) {
+ idToken = strings.TrimSpace(idToken)
+ if idToken == "" {
+ return nil, errors.New("missing id_token")
+ }
+ allowed := oidcAllowedSigningAlgs(cfg.AllowedSigningAlgs)
+ if len(allowed) == 0 {
+ return nil, errors.New("empty allowed signing algorithms")
+ }
+
+ jwks, err := oidcFetchJWKSet(ctx, cfg.JWKSURL)
+ if err != nil {
+ return nil, err
+ }
+ leeway := time.Duration(cfg.ClockSkewSeconds) * time.Second
+ claims := &oidcIDTokenClaims{}
+
+ parsed, err := jwt.ParseWithClaims(
+ idToken,
+ claims,
+ func(token *jwt.Token) (any, error) {
+ alg := strings.TrimSpace(token.Method.Alg())
+ if !containsString(allowed, alg) {
+ return nil, fmt.Errorf("unexpected signing algorithm: %s", alg)
+ }
+ kid, _ := token.Header["kid"].(string)
+ return oidcFindPublicKey(jwks, strings.TrimSpace(kid), alg)
+ },
+ jwt.WithValidMethods(allowed),
+ jwt.WithAudience(cfg.ClientID),
+ jwt.WithIssuer(cfg.IssuerURL),
+ jwt.WithLeeway(leeway),
+ )
+ if err != nil {
+ return nil, err
+ }
+ if !parsed.Valid {
+ return nil, errors.New("id_token invalid")
+ }
+ if strings.TrimSpace(claims.Subject) == "" {
+ return nil, errors.New("id_token missing sub")
+ }
+ if expectedNonce != "" && strings.TrimSpace(claims.Nonce) != strings.TrimSpace(expectedNonce) {
+ return nil, errors.New("id_token nonce mismatch")
+ }
+ if len(claims.Audience) > 1 {
+ if strings.TrimSpace(claims.Azp) == "" || strings.TrimSpace(claims.Azp) != strings.TrimSpace(cfg.ClientID) {
+ return nil, errors.New("id_token azp mismatch")
+ }
+ }
+ return claims, nil
+}
+
+func oidcAllowedSigningAlgs(raw string) []string {
+ if strings.TrimSpace(raw) == "" {
+ return []string{"RS256", "ES256", "PS256"}
+ }
+ seen := make(map[string]struct{})
+ out := make([]string, 0, 4)
+ for _, part := range strings.Split(raw, ",") {
+ alg := strings.ToUpper(strings.TrimSpace(part))
+ if alg == "" {
+ continue
+ }
+ if _, ok := seen[alg]; ok {
+ continue
+ }
+ seen[alg] = struct{}{}
+ out = append(out, alg)
+ }
+ return out
+}
+
+func oidcFetchJWKSet(ctx context.Context, jwksURL string) (*oidcJWKSet, error) {
+ jwksURL = strings.TrimSpace(jwksURL)
+ if jwksURL == "" {
+ return nil, errors.New("missing jwks_url")
+ }
+ resp, err := req.C().
+ SetTimeout(30*time.Second).
+ R().
+ SetContext(ctx).
+ SetHeader("Accept", "application/json").
+ Get(jwksURL)
+ if err != nil {
+ return nil, fmt.Errorf("request jwks: %w", err)
+ }
+ if !resp.IsSuccessState() {
+ return nil, fmt.Errorf("jwks status=%d", resp.StatusCode)
+ }
+ set := &oidcJWKSet{}
+ if err := json.Unmarshal(resp.Bytes(), set); err != nil {
+ return nil, fmt.Errorf("parse jwks: %w", err)
+ }
+ if len(set.Keys) == 0 {
+ return nil, errors.New("jwks empty keys")
+ }
+ return set, nil
+}
+
+func oidcFindPublicKey(set *oidcJWKSet, kid, alg string) (any, error) {
+ if set == nil {
+ return nil, errors.New("jwks not loaded")
+ }
+ alg = strings.ToUpper(strings.TrimSpace(alg))
+ kid = strings.TrimSpace(kid)
+
+ var lastErr error
+ for i := range set.Keys {
+ k := set.Keys[i]
+ if strings.TrimSpace(k.Use) != "" && !strings.EqualFold(strings.TrimSpace(k.Use), "sig") {
+ continue
+ }
+ if kid != "" && strings.TrimSpace(k.Kid) != kid {
+ continue
+ }
+ if strings.TrimSpace(k.Alg) != "" && !strings.EqualFold(strings.TrimSpace(k.Alg), alg) {
+ continue
+ }
+ pk, err := k.publicKey()
+ if err != nil {
+ lastErr = err
+ continue
+ }
+ if pk != nil {
+ return pk, nil
+ }
+ }
+ if lastErr != nil {
+ return nil, lastErr
+ }
+ if kid != "" {
+ return nil, fmt.Errorf("jwk not found for kid=%s", kid)
+ }
+ return nil, errors.New("jwk not found")
+}
+
+func (k oidcJWK) publicKey() (any, error) {
+ switch strings.ToUpper(strings.TrimSpace(k.Kty)) {
+ case "RSA":
+ n, err := decodeBase64URLBigInt(k.N)
+ if err != nil {
+ return nil, fmt.Errorf("decode rsa n: %w", err)
+ }
+ eBytes, err := base64.RawURLEncoding.DecodeString(strings.TrimSpace(k.E))
+ if err != nil {
+ return nil, fmt.Errorf("decode rsa e: %w", err)
+ }
+ if len(eBytes) == 0 {
+ return nil, errors.New("empty rsa e")
+ }
+ e := 0
+ for _, b := range eBytes {
+ e = (e << 8) | int(b)
+ }
+ if e <= 0 {
+ return nil, errors.New("invalid rsa exponent")
+ }
+ if n.Sign() <= 0 {
+ return nil, errors.New("invalid rsa modulus")
+ }
+ return &rsa.PublicKey{N: n, E: e}, nil
+ case "EC":
+ var curve elliptic.Curve
+ switch strings.TrimSpace(k.Crv) {
+ case "P-256":
+ curve = elliptic.P256()
+ case "P-384":
+ curve = elliptic.P384()
+ case "P-521":
+ curve = elliptic.P521()
+ default:
+ return nil, fmt.Errorf("unsupported ec curve: %s", k.Crv)
+ }
+ x, err := decodeBase64URLBigInt(k.X)
+ if err != nil {
+ return nil, fmt.Errorf("decode ec x: %w", err)
+ }
+ y, err := decodeBase64URLBigInt(k.Y)
+ if err != nil {
+ return nil, fmt.Errorf("decode ec y: %w", err)
+ }
+ if !curve.IsOnCurve(x, y) {
+ return nil, errors.New("ec point is not on curve")
+ }
+ return &ecdsa.PublicKey{Curve: curve, X: x, Y: y}, nil
+ default:
+ return nil, fmt.Errorf("unsupported jwk kty: %s", k.Kty)
+ }
+}
+
+func decodeBase64URLBigInt(raw string) (*big.Int, error) {
+ buf, err := base64.RawURLEncoding.DecodeString(strings.TrimSpace(raw))
+ if err != nil {
+ return nil, err
+ }
+ if len(buf) == 0 {
+ return nil, errors.New("empty value")
+ }
+ return new(big.Int).SetBytes(buf), nil
+}
+
+func containsString(values []string, target string) bool {
+ target = strings.TrimSpace(target)
+ for _, v := range values {
+ if strings.EqualFold(strings.TrimSpace(v), target) {
+ return true
+ }
+ }
+ return false
+}
+
+func oidcIdentityKey(issuer, subject string) string {
+ issuer = strings.TrimSpace(strings.ToLower(issuer))
+ subject = strings.TrimSpace(subject)
+ return issuer + "\x1f" + subject
+}
+
+func oidcSyntheticEmailFromIdentityKey(identityKey string) string {
+ identityKey = strings.TrimSpace(identityKey)
+ if identityKey == "" {
+ return ""
+ }
+ sum := sha256.Sum256([]byte(identityKey))
+ return "oidc-" + hex.EncodeToString(sum[:16]) + service.OIDCConnectSyntheticEmailDomain
+}
+
+func oidcSelectLoginEmail(userInfoEmail, idTokenEmail, identityKey string) string {
+ email := strings.TrimSpace(firstNonEmpty(userInfoEmail, idTokenEmail))
+ if email != "" {
+ return email
+ }
+ return oidcSyntheticEmailFromIdentityKey(identityKey)
+}
+
+func oidcFallbackUsername(subject string) string {
+ subject = strings.TrimSpace(subject)
+ if subject == "" {
+ return "oidc_user"
+ }
+ sum := sha256.Sum256([]byte(subject))
+ return "oidc_" + hex.EncodeToString(sum[:])[:12]
+}
+
+func oidcSetCookie(c *gin.Context, name, value string, maxAgeSec int, secure bool) {
+ http.SetCookie(c.Writer, &http.Cookie{
+ Name: name,
+ Value: value,
+ Path: oidcOAuthCookiePath,
+ MaxAge: maxAgeSec,
+ HttpOnly: true,
+ Secure: secure,
+ SameSite: http.SameSiteLaxMode,
+ })
+}
+
+func oidcClearCookie(c *gin.Context, name string, secure bool) {
+ http.SetCookie(c.Writer, &http.Cookie{
+ Name: name,
+ Value: "",
+ Path: oidcOAuthCookiePath,
+ MaxAge: -1,
+ HttpOnly: true,
+ Secure: secure,
+ SameSite: http.SameSiteLaxMode,
+ })
+}
diff --git a/backend/internal/handler/auth_oidc_oauth_test.go b/backend/internal/handler/auth_oidc_oauth_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..a161aa77cf6faefaebae46f6a570227263484c90
--- /dev/null
+++ b/backend/internal/handler/auth_oidc_oauth_test.go
@@ -0,0 +1,120 @@
+package handler
+
+import (
+ "context"
+ "crypto/rand"
+ "crypto/rsa"
+ "encoding/base64"
+ "encoding/json"
+ "math/big"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+ "time"
+
+ "github.com/Wei-Shaw/sub2api/internal/config"
+ "github.com/golang-jwt/jwt/v5"
+ "github.com/stretchr/testify/require"
+)
+
+func TestOIDCSyntheticEmailStableAndDistinct(t *testing.T) {
+ k1 := oidcIdentityKey("https://issuer.example.com", "subject-a")
+ k2 := oidcIdentityKey("https://issuer.example.com", "subject-b")
+
+ e1 := oidcSyntheticEmailFromIdentityKey(k1)
+ e1Again := oidcSyntheticEmailFromIdentityKey(k1)
+ e2 := oidcSyntheticEmailFromIdentityKey(k2)
+
+ require.Equal(t, e1, e1Again)
+ require.NotEqual(t, e1, e2)
+ require.Contains(t, e1, "@oidc-connect.invalid")
+}
+
+func TestOIDCSelectLoginEmailPrefersRealEmail(t *testing.T) {
+ identityKey := oidcIdentityKey("https://issuer.example.com", "subject-a")
+
+ email := oidcSelectLoginEmail("user@example.com", "idtoken@example.com", identityKey)
+ require.Equal(t, "user@example.com", email)
+
+ email = oidcSelectLoginEmail("", "idtoken@example.com", identityKey)
+ require.Equal(t, "idtoken@example.com", email)
+
+ email = oidcSelectLoginEmail("", "", identityKey)
+ require.Contains(t, email, "@oidc-connect.invalid")
+ require.Equal(t, oidcSyntheticEmailFromIdentityKey(identityKey), email)
+}
+
+func TestBuildOIDCAuthorizeURLIncludesNonceAndPKCE(t *testing.T) {
+ cfg := config.OIDCConnectConfig{
+ AuthorizeURL: "https://issuer.example.com/auth",
+ ClientID: "cid",
+ Scopes: "openid email profile",
+ UsePKCE: true,
+ }
+
+ u, err := buildOIDCAuthorizeURL(cfg, "state123", "nonce123", "challenge123", "https://app.example.com/callback")
+ require.NoError(t, err)
+ require.Contains(t, u, "nonce=nonce123")
+ require.Contains(t, u, "code_challenge=challenge123")
+ require.Contains(t, u, "code_challenge_method=S256")
+ require.Contains(t, u, "scope=openid+email+profile")
+}
+
+func TestOIDCParseAndValidateIDToken(t *testing.T) {
+ priv, err := rsa.GenerateKey(rand.Reader, 2048)
+ require.NoError(t, err)
+
+ kid := "kid-1"
+ jwks := oidcJWKSet{Keys: []oidcJWK{buildRSAJWK(kid, &priv.PublicKey)}}
+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ require.NoError(t, json.NewEncoder(w).Encode(jwks))
+ }))
+ defer srv.Close()
+
+ now := time.Now()
+ claims := oidcIDTokenClaims{
+ Nonce: "nonce-ok",
+ Azp: "client-1",
+ RegisteredClaims: jwt.RegisteredClaims{
+ Issuer: "https://issuer.example.com",
+ Subject: "subject-1",
+ Audience: jwt.ClaimStrings{"client-1", "another-aud"},
+ IssuedAt: jwt.NewNumericDate(now),
+ NotBefore: jwt.NewNumericDate(now.Add(-30 * time.Second)),
+ ExpiresAt: jwt.NewNumericDate(now.Add(5 * time.Minute)),
+ },
+ }
+ tok := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
+ tok.Header["kid"] = kid
+ signed, err := tok.SignedString(priv)
+ require.NoError(t, err)
+
+ cfg := config.OIDCConnectConfig{
+ ClientID: "client-1",
+ IssuerURL: "https://issuer.example.com",
+ JWKSURL: srv.URL,
+ AllowedSigningAlgs: "RS256",
+ ClockSkewSeconds: 120,
+ }
+
+ parsed, err := oidcParseAndValidateIDToken(context.Background(), cfg, signed, "nonce-ok")
+ require.NoError(t, err)
+ require.Equal(t, "subject-1", parsed.Subject)
+ require.Equal(t, "https://issuer.example.com", parsed.Issuer)
+
+ _, err = oidcParseAndValidateIDToken(context.Background(), cfg, signed, "bad-nonce")
+ require.Error(t, err)
+}
+
+func buildRSAJWK(kid string, pub *rsa.PublicKey) oidcJWK {
+ n := base64.RawURLEncoding.EncodeToString(pub.N.Bytes())
+ e := base64.RawURLEncoding.EncodeToString(big.NewInt(int64(pub.E)).Bytes())
+ return oidcJWK{
+ Kty: "RSA",
+ Kid: kid,
+ Use: "sig",
+ Alg: "RS256",
+ N: n,
+ E: e,
+ }
+}
diff --git a/backend/internal/handler/dto/mappers.go b/backend/internal/handler/dto/mappers.go
index 2eab670e759605cc31cfa9c75a20b40683e6b8fa..478600eb8c9a9e53c3c909c8c49002786c9b0a24 100644
--- a/backend/internal/handler/dto/mappers.go
+++ b/backend/internal/handler/dto/mappers.go
@@ -133,16 +133,17 @@ func GroupFromServiceAdmin(g *service.Group) *AdminGroup {
return nil
}
out := &AdminGroup{
- Group: groupFromServiceBase(g),
- ModelRouting: g.ModelRouting,
- ModelRoutingEnabled: g.ModelRoutingEnabled,
- MCPXMLInject: g.MCPXMLInject,
- DefaultMappedModel: g.DefaultMappedModel,
- SupportedModelScopes: g.SupportedModelScopes,
- AccountCount: g.AccountCount,
- ActiveAccountCount: g.ActiveAccountCount,
- RateLimitedAccountCount: g.RateLimitedAccountCount,
- SortOrder: g.SortOrder,
+ Group: groupFromServiceBase(g),
+ ModelRouting: g.ModelRouting,
+ ModelRoutingEnabled: g.ModelRoutingEnabled,
+ MCPXMLInject: g.MCPXMLInject,
+ DefaultMappedModel: g.DefaultMappedModel,
+ MessagesDispatchModelConfig: g.MessagesDispatchModelConfig,
+ SupportedModelScopes: g.SupportedModelScopes,
+ AccountCount: g.AccountCount,
+ ActiveAccountCount: g.ActiveAccountCount,
+ RateLimitedAccountCount: g.RateLimitedAccountCount,
+ SortOrder: g.SortOrder,
}
if len(g.AccountGroups) > 0 {
out.AccountGroups = make([]AccountGroup, 0, len(g.AccountGroups))
diff --git a/backend/internal/handler/dto/settings.go b/backend/internal/handler/dto/settings.go
index 73707f79a57d5af4e698718c50e78fd87e7efb40..cbbe92160d3930bb08ef91f84b48dbfb848fbb38 100644
--- a/backend/internal/handler/dto/settings.go
+++ b/backend/internal/handler/dto/settings.go
@@ -51,6 +51,29 @@ type SystemSettings struct {
LinuxDoConnectClientSecretConfigured bool `json:"linuxdo_connect_client_secret_configured"`
LinuxDoConnectRedirectURL string `json:"linuxdo_connect_redirect_url"`
+ OIDCConnectEnabled bool `json:"oidc_connect_enabled"`
+ OIDCConnectProviderName string `json:"oidc_connect_provider_name"`
+ OIDCConnectClientID string `json:"oidc_connect_client_id"`
+ OIDCConnectClientSecretConfigured bool `json:"oidc_connect_client_secret_configured"`
+ OIDCConnectIssuerURL string `json:"oidc_connect_issuer_url"`
+ OIDCConnectDiscoveryURL string `json:"oidc_connect_discovery_url"`
+ OIDCConnectAuthorizeURL string `json:"oidc_connect_authorize_url"`
+ OIDCConnectTokenURL string `json:"oidc_connect_token_url"`
+ OIDCConnectUserInfoURL string `json:"oidc_connect_userinfo_url"`
+ OIDCConnectJWKSURL string `json:"oidc_connect_jwks_url"`
+ OIDCConnectScopes string `json:"oidc_connect_scopes"`
+ OIDCConnectRedirectURL string `json:"oidc_connect_redirect_url"`
+ OIDCConnectFrontendRedirectURL string `json:"oidc_connect_frontend_redirect_url"`
+ OIDCConnectTokenAuthMethod string `json:"oidc_connect_token_auth_method"`
+ OIDCConnectUsePKCE bool `json:"oidc_connect_use_pkce"`
+ OIDCConnectValidateIDToken bool `json:"oidc_connect_validate_id_token"`
+ OIDCConnectAllowedSigningAlgs string `json:"oidc_connect_allowed_signing_algs"`
+ OIDCConnectClockSkewSeconds int `json:"oidc_connect_clock_skew_seconds"`
+ OIDCConnectRequireEmailVerified bool `json:"oidc_connect_require_email_verified"`
+ OIDCConnectUserInfoEmailPath string `json:"oidc_connect_userinfo_email_path"`
+ OIDCConnectUserInfoIDPath string `json:"oidc_connect_userinfo_id_path"`
+ OIDCConnectUserInfoUsernamePath string `json:"oidc_connect_userinfo_username_path"`
+
SiteName string `json:"site_name"`
SiteLogo string `json:"site_logo"`
SiteSubtitle string `json:"site_subtitle"`
@@ -61,6 +84,8 @@ type SystemSettings struct {
HideCcsImportButton bool `json:"hide_ccs_import_button"`
PurchaseSubscriptionEnabled bool `json:"purchase_subscription_enabled"`
PurchaseSubscriptionURL string `json:"purchase_subscription_url"`
+ TableDefaultPageSize int `json:"table_default_page_size"`
+ TablePageSizeOptions []int `json:"table_page_size_options"`
CustomMenuItems []CustomMenuItem `json:"custom_menu_items"`
CustomEndpoints []CustomEndpoint `json:"custom_endpoints"`
@@ -98,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 {
@@ -125,10 +172,16 @@ type PublicSettings struct {
HideCcsImportButton bool `json:"hide_ccs_import_button"`
PurchaseSubscriptionEnabled bool `json:"purchase_subscription_enabled"`
PurchaseSubscriptionURL string `json:"purchase_subscription_url"`
+ TableDefaultPageSize int `json:"table_default_page_size"`
+ TablePageSizeOptions []int `json:"table_page_size_options"`
CustomMenuItems []CustomMenuItem `json:"custom_menu_items"`
CustomEndpoints []CustomEndpoint `json:"custom_endpoints"`
LinuxDoOAuthEnabled bool `json:"linuxdo_oauth_enabled"`
+ OIDCOAuthEnabled bool `json:"oidc_oauth_enabled"`
+ 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"`
}
diff --git a/backend/internal/handler/dto/types.go b/backend/internal/handler/dto/types.go
index 82065deb724da9cc0e5cbe8f7fb5025c274234e8..e026ca655180d1577045d41ea4b41667f9deee1c 100644
--- a/backend/internal/handler/dto/types.go
+++ b/backend/internal/handler/dto/types.go
@@ -1,6 +1,10 @@
package dto
-import "time"
+import (
+ "time"
+
+ "github.com/Wei-Shaw/sub2api/internal/domain"
+)
type User struct {
ID int64 `json:"id"`
@@ -112,7 +116,8 @@ type AdminGroup struct {
MCPXMLInject bool `json:"mcp_xml_inject"`
// OpenAI Messages 调度配置(仅 openai 平台使用)
- DefaultMappedModel string `json:"default_mapped_model"`
+ DefaultMappedModel string `json:"default_mapped_model"`
+ MessagesDispatchModelConfig domain.OpenAIMessagesDispatchModelConfig `json:"messages_dispatch_model_config"`
// 支持的模型系列(仅 antigravity 平台使用)
SupportedModelScopes []string `json:"supported_model_scopes"`
diff --git a/backend/internal/handler/gateway_handler_warmup_intercept_unit_test.go b/backend/internal/handler/gateway_handler_warmup_intercept_unit_test.go
index 4caef9551ba8d3c5100c6d6baa3c4e954c8179d4..acea37804f4679d4f102c061acf77e71c6e7fe5f 100644
--- a/backend/internal/handler/gateway_handler_warmup_intercept_unit_test.go
+++ b/backend/internal/handler/gateway_handler_warmup_intercept_unit_test.go
@@ -34,7 +34,12 @@ func (f *fakeSchedulerCache) GetSnapshot(_ context.Context, _ service.SchedulerB
func (f *fakeSchedulerCache) SetSnapshot(_ context.Context, _ service.SchedulerBucket, _ []service.Account) error {
return nil
}
-func (f *fakeSchedulerCache) GetAccount(_ context.Context, _ int64) (*service.Account, error) {
+func (f *fakeSchedulerCache) GetAccount(_ context.Context, id int64) (*service.Account, error) {
+ for _, account := range f.accounts {
+ if account != nil && account.ID == id {
+ return account, nil
+ }
+ }
return nil, nil
}
func (f *fakeSchedulerCache) SetAccount(_ context.Context, _ *service.Account) error { return nil }
diff --git a/backend/internal/handler/handler.go b/backend/internal/handler/handler.go
index d4c349fb6583e03469e17b0cc2967a2f765c528e..906a74f148fef7a37080500a84a6db2a541984d7 100644
--- a/backend/internal/handler/handler.go
+++ b/backend/internal/handler/handler.go
@@ -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
diff --git a/backend/internal/handler/openai_gateway_handler.go b/backend/internal/handler/openai_gateway_handler.go
index 4747ccfe1e19d5c51d021bb08ee2675fd93c904a..5319b55d9dddf85a13200e749ee00a15128f1de1 100644
--- a/backend/internal/handler/openai_gateway_handler.go
+++ b/backend/internal/handler/openai_gateway_handler.go
@@ -47,6 +47,13 @@ func resolveOpenAIForwardDefaultMappedModel(apiKey *service.APIKey, fallbackMode
return strings.TrimSpace(apiKey.Group.DefaultMappedModel)
}
+func resolveOpenAIMessagesDispatchMappedModel(apiKey *service.APIKey, requestedModel string) string {
+ if apiKey == nil || apiKey.Group == nil {
+ return ""
+ }
+ return strings.TrimSpace(apiKey.Group.ResolveMessagesDispatchModel(requestedModel))
+}
+
// NewOpenAIGatewayHandler creates a new OpenAIGatewayHandler
func NewOpenAIGatewayHandler(
gatewayService *service.OpenAIGatewayService,
@@ -551,6 +558,7 @@ func (h *OpenAIGatewayHandler) Messages(c *gin.Context) {
}
reqModel := modelResult.String()
routingModel := service.NormalizeOpenAICompatRequestedModel(reqModel)
+ preferredMappedModel := resolveOpenAIMessagesDispatchMappedModel(apiKey, reqModel)
reqStream := gjson.GetBytes(body, "stream").Bool()
reqLog = reqLog.With(zap.String("model", reqModel), zap.Bool("stream", reqStream))
@@ -609,17 +617,20 @@ func (h *OpenAIGatewayHandler) Messages(c *gin.Context) {
failedAccountIDs := make(map[int64]struct{})
sameAccountRetryCount := make(map[int64]int)
var lastFailoverErr *service.UpstreamFailoverError
+ effectiveMappedModel := preferredMappedModel
for {
- // 清除上一次迭代的降级模型标记,避免残留影响本次迭代
- c.Set("openai_messages_fallback_model", "")
+ currentRoutingModel := routingModel
+ if effectiveMappedModel != "" {
+ currentRoutingModel = effectiveMappedModel
+ }
reqLog.Debug("openai_messages.account_selecting", zap.Int("excluded_account_count", len(failedAccountIDs)))
selection, scheduleDecision, err := h.gatewayService.SelectAccountWithScheduler(
c.Request.Context(),
apiKey.GroupID,
"", // no previous_response_id
sessionHash,
- routingModel,
+ currentRoutingModel,
failedAccountIDs,
service.OpenAIUpstreamTransportAny,
)
@@ -628,29 +639,7 @@ func (h *OpenAIGatewayHandler) Messages(c *gin.Context) {
zap.Error(err),
zap.Int("excluded_account_count", len(failedAccountIDs)),
)
- // 首次调度失败 + 有默认映射模型 → 用默认模型重试
if len(failedAccountIDs) == 0 {
- defaultModel := ""
- if apiKey.Group != nil {
- defaultModel = apiKey.Group.DefaultMappedModel
- }
- if defaultModel != "" && defaultModel != routingModel {
- reqLog.Info("openai_messages.fallback_to_default_model",
- zap.String("default_mapped_model", defaultModel),
- )
- selection, scheduleDecision, err = h.gatewayService.SelectAccountWithScheduler(
- c.Request.Context(),
- apiKey.GroupID,
- "",
- sessionHash,
- defaultModel,
- failedAccountIDs,
- service.OpenAIUpstreamTransportAny,
- )
- if err == nil && selection != nil {
- c.Set("openai_messages_fallback_model", defaultModel)
- }
- }
if err != nil {
h.anthropicStreamingAwareError(c, http.StatusServiceUnavailable, "api_error", "Service temporarily unavailable", streamStarted)
return
@@ -682,9 +671,7 @@ func (h *OpenAIGatewayHandler) Messages(c *gin.Context) {
service.SetOpsLatencyMs(c, service.OpsRoutingLatencyMsKey, time.Since(routingStart).Milliseconds())
forwardStart := time.Now()
- // Forward 层需要始终拿到 group 默认映射模型,这样未命中账号级映射的
- // Claude 兼容模型才不会在后续 Codex 规范化中意外退化到 gpt-5.1。
- defaultMappedModel := resolveOpenAIForwardDefaultMappedModel(apiKey, c.GetString("openai_messages_fallback_model"))
+ defaultMappedModel := strings.TrimSpace(effectiveMappedModel)
// 应用渠道模型映射到请求体
forwardBody := body
if channelMappingMsg.Mapped {
diff --git a/backend/internal/handler/openai_gateway_handler_test.go b/backend/internal/handler/openai_gateway_handler_test.go
index 7bbf94ecb376ab7e18629d492e7917e119ee94e0..d299fb81e338120a49052581b8dd00ac813c6ab1 100644
--- a/backend/internal/handler/openai_gateway_handler_test.go
+++ b/backend/internal/handler/openai_gateway_handler_test.go
@@ -360,7 +360,7 @@ func TestResolveOpenAIForwardDefaultMappedModel(t *testing.T) {
require.Equal(t, "gpt-5.2", resolveOpenAIForwardDefaultMappedModel(apiKey, " gpt-5.2 "))
})
- t.Run("uses_group_default_on_normal_path", func(t *testing.T) {
+ t.Run("uses_group_default_when_explicit_fallback_absent", func(t *testing.T) {
apiKey := &service.APIKey{
Group: &service.Group{DefaultMappedModel: "gpt-5.4"},
}
@@ -376,6 +376,45 @@ func TestResolveOpenAIForwardDefaultMappedModel(t *testing.T) {
})
}
+func TestResolveOpenAIMessagesDispatchMappedModel(t *testing.T) {
+ t.Run("exact_claude_model_override_wins", func(t *testing.T) {
+ apiKey := &service.APIKey{
+ Group: &service.Group{
+ MessagesDispatchModelConfig: service.OpenAIMessagesDispatchModelConfig{
+ SonnetMappedModel: "gpt-5.2",
+ ExactModelMappings: map[string]string{
+ "claude-sonnet-4-5-20250929": "gpt-5.4-mini-high",
+ },
+ },
+ },
+ }
+ require.Equal(t, "gpt-5.4-mini", resolveOpenAIMessagesDispatchMappedModel(apiKey, "claude-sonnet-4-5-20250929"))
+ })
+
+ t.Run("uses_family_default_when_no_override", func(t *testing.T) {
+ apiKey := &service.APIKey{Group: &service.Group{}}
+ require.Equal(t, "gpt-5.4", resolveOpenAIMessagesDispatchMappedModel(apiKey, "claude-opus-4-6"))
+ require.Equal(t, "gpt-5.3-codex", resolveOpenAIMessagesDispatchMappedModel(apiKey, "claude-sonnet-4-5-20250929"))
+ require.Equal(t, "gpt-5.4-mini", resolveOpenAIMessagesDispatchMappedModel(apiKey, "claude-haiku-4-5-20251001"))
+ })
+
+ t.Run("returns_empty_for_non_claude_or_missing_group", func(t *testing.T) {
+ require.Empty(t, resolveOpenAIMessagesDispatchMappedModel(nil, "claude-sonnet-4-5-20250929"))
+ require.Empty(t, resolveOpenAIMessagesDispatchMappedModel(&service.APIKey{}, "claude-sonnet-4-5-20250929"))
+ require.Empty(t, resolveOpenAIMessagesDispatchMappedModel(&service.APIKey{Group: &service.Group{}}, "gpt-5.4"))
+ })
+
+ t.Run("does_not_fall_back_to_group_default_mapped_model", func(t *testing.T) {
+ apiKey := &service.APIKey{
+ Group: &service.Group{
+ DefaultMappedModel: "gpt-5.4",
+ },
+ }
+ require.Empty(t, resolveOpenAIMessagesDispatchMappedModel(apiKey, "gpt-5.4"))
+ require.Equal(t, "gpt-5.3-codex", resolveOpenAIMessagesDispatchMappedModel(apiKey, "claude-sonnet-4-5-20250929"))
+ })
+}
+
func TestOpenAIResponses_MissingDependencies_ReturnsServiceUnavailable(t *testing.T) {
gin.SetMode(gin.TestMode)
diff --git a/backend/internal/handler/payment_handler.go b/backend/internal/handler/payment_handler.go
new file mode 100644
index 0000000000000000000000000000000000000000..0425fc49c7fccf11a0621ebc41b5cb00ee7130e5
--- /dev/null
+++ b/backend/internal/handler/payment_handler.go
@@ -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
+}
diff --git a/backend/internal/handler/payment_webhook_handler.go b/backend/internal/handler/payment_webhook_handler.go
new file mode 100644
index 0000000000000000000000000000000000000000..8a83bfebb72830b9957c83f4cf419814ffbfc2a0
--- /dev/null
+++ b/backend/internal/handler/payment_webhook_handler.go
@@ -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")
+ }
+}
diff --git a/backend/internal/handler/payment_webhook_handler_test.go b/backend/internal/handler/payment_webhook_handler_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..bdef1766d91108a7ab4656bfc29715667a3a40aa
--- /dev/null
+++ b/backend/internal/handler/payment_webhook_handler_test.go
@@ -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)
+ })
+}
diff --git a/backend/internal/handler/setting_handler.go b/backend/internal/handler/setting_handler.go
index 977c2301bbf078b093a100b1b47d195121443c96..54a92a8c78592c10a05ea16d34fc22d4a9c5b3e1 100644
--- a/backend/internal/handler/setting_handler.go
+++ b/backend/internal/handler/setting_handler.go
@@ -51,10 +51,15 @@ func (h *SettingHandler) GetPublicSettings(c *gin.Context) {
HideCcsImportButton: settings.HideCcsImportButton,
PurchaseSubscriptionEnabled: settings.PurchaseSubscriptionEnabled,
PurchaseSubscriptionURL: settings.PurchaseSubscriptionURL,
+ TableDefaultPageSize: settings.TableDefaultPageSize,
+ TablePageSizeOptions: settings.TablePageSizeOptions,
CustomMenuItems: dto.ParseUserVisibleMenuItems(settings.CustomMenuItems),
CustomEndpoints: dto.ParseCustomEndpoints(settings.CustomEndpoints),
LinuxDoOAuthEnabled: settings.LinuxDoOAuthEnabled,
+ OIDCOAuthEnabled: settings.OIDCOAuthEnabled,
+ OIDCOAuthProviderName: settings.OIDCOAuthProviderName,
BackendModeEnabled: settings.BackendModeEnabled,
+ PaymentEnabled: settings.PaymentEnabled,
Version: h.version,
})
}
diff --git a/backend/internal/handler/usage_handler.go b/backend/internal/handler/usage_handler.go
index 483f51059b764f8a7eacd14fb35bfcabd51b5e4d..b8506154365bf6643a8a57761bae93e7f13bf1cd 100644
--- a/backend/internal/handler/usage_handler.go
+++ b/backend/internal/handler/usage_handler.go
@@ -119,7 +119,12 @@ func (h *UsageHandler) List(c *gin.Context) {
endTime = &t
}
- params := pagination.PaginationParams{Page: page, PageSize: pageSize}
+ params := pagination.PaginationParams{
+ Page: page,
+ PageSize: pageSize,
+ SortBy: c.DefaultQuery("sort_by", "created_at"),
+ SortOrder: c.DefaultQuery("sort_order", "desc"),
+ }
filters := usagestats.UsageLogFilters{
UserID: subject.UserID, // Always filter by current user for security
APIKeyID: apiKeyID,
diff --git a/backend/internal/handler/usage_handler_request_type_test.go b/backend/internal/handler/usage_handler_request_type_test.go
index 7c4c79135a6976bf706e9342f556a7bdc4176851..b49ed59ba36b974dee684854f194504166ada056 100644
--- a/backend/internal/handler/usage_handler_request_type_test.go
+++ b/backend/internal/handler/usage_handler_request_type_test.go
@@ -16,10 +16,12 @@ import (
type userUsageRepoCapture struct {
service.UsageLogRepository
+ listParams pagination.PaginationParams
listFilters usagestats.UsageLogFilters
}
func (s *userUsageRepoCapture) ListWithFilters(ctx context.Context, params pagination.PaginationParams, filters usagestats.UsageLogFilters) ([]service.UsageLog, *pagination.PaginationResult, error) {
+ s.listParams = params
s.listFilters = filters
return []service.UsageLog{}, &pagination.PaginationResult{
Total: 0,
diff --git a/backend/internal/handler/usage_handler_sort_test.go b/backend/internal/handler/usage_handler_sort_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..1af313b09d295223cd7ca7b07278bc5de3d7bc26
--- /dev/null
+++ b/backend/internal/handler/usage_handler_sort_test.go
@@ -0,0 +1,35 @@
+package handler
+
+import (
+ "net/http"
+ "net/http/httptest"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+func TestUserUsageListSortParams(t *testing.T) {
+ repo := &userUsageRepoCapture{}
+ router := newUserUsageRequestTypeTestRouter(repo)
+
+ req := httptest.NewRequest(http.MethodGet, "/usage?sort_by=model&sort_order=ASC", nil)
+ rec := httptest.NewRecorder()
+ router.ServeHTTP(rec, req)
+
+ require.Equal(t, http.StatusOK, rec.Code)
+ require.Equal(t, "model", repo.listParams.SortBy)
+ require.Equal(t, "ASC", repo.listParams.SortOrder)
+}
+
+func TestUserUsageListSortDefaults(t *testing.T) {
+ repo := &userUsageRepoCapture{}
+ router := newUserUsageRequestTypeTestRouter(repo)
+
+ req := httptest.NewRequest(http.MethodGet, "/usage", nil)
+ rec := httptest.NewRecorder()
+ router.ServeHTTP(rec, req)
+
+ require.Equal(t, http.StatusOK, rec.Code)
+ require.Equal(t, "created_at", repo.listParams.SortBy)
+ require.Equal(t, "desc", repo.listParams.SortOrder)
+}
diff --git a/backend/internal/handler/wire.go b/backend/internal/handler/wire.go
index d9622594f2c784d88c721d19bbc439d360e0b481..4b54d41ad32c28cc79582eb458459031da55c875 100644
--- a/backend/internal/handler/wire.go
+++ b/backend/internal/handler/wire.go
@@ -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,
diff --git a/backend/internal/payment/amount.go b/backend/internal/payment/amount.go
new file mode 100644
index 0000000000000000000000000000000000000000..8489ffa326533568f665cd19e899560658600da5
--- /dev/null
+++ b/backend/internal/payment/amount.go
@@ -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()
+}
diff --git a/backend/internal/payment/amount_test.go b/backend/internal/payment/amount_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..6120b1894add46046d573651f55900d116b0c57a
--- /dev/null
+++ b/backend/internal/payment/amount_test.go
@@ -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)
+ }
+ }
+}
diff --git a/backend/internal/payment/crypto.go b/backend/internal/payment/crypto.go
new file mode 100644
index 0000000000000000000000000000000000000000..e39e957f4d29e2efd1dad9ce5e709f76764f0454
--- /dev/null
+++ b/backend/internal/payment/crypto.go
@@ -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
+}
diff --git a/backend/internal/payment/crypto_test.go b/backend/internal/payment/crypto_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..da8b6006e1e9b648d3466ff93454a67e6aaa869b
--- /dev/null
+++ b/backend/internal/payment/crypto_test.go
@@ -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)
+ }
+ }
+}
diff --git a/backend/internal/payment/fee.go b/backend/internal/payment/fee.go
new file mode 100644
index 0000000000000000000000000000000000000000..e2128e5efa33e2f2de18b7f67c209d94b6f60a9e
--- /dev/null
+++ b/backend/internal/payment/fee.go
@@ -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)
+}
diff --git a/backend/internal/payment/fee_test.go b/backend/internal/payment/fee_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..c58d1082a91f1f0f06bd100c7fc89c6edb5d1760
--- /dev/null
+++ b/backend/internal/payment/fee_test.go
@@ -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)
+ }
+ })
+ }
+}
diff --git a/backend/internal/payment/load_balancer.go b/backend/internal/payment/load_balancer.go
new file mode 100644
index 0000000000000000000000000000000000000000..afe607e0cfad50ba009050a1c073dc9520467d0d
--- /dev/null
+++ b/backend/internal/payment/load_balancer.go
@@ -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)
+}
diff --git a/backend/internal/payment/load_balancer_test.go b/backend/internal/payment/load_balancer_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..568b56a30debac86ebac9e019dd81cdfffd16a1d
--- /dev/null
+++ b/backend/internal/payment/load_balancer_test.go
@@ -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
+}
diff --git a/backend/internal/payment/provider/alipay.go b/backend/internal/payment/provider/alipay.go
new file mode 100644
index 0000000000000000000000000000000000000000..3eca0b2cc2cac8e7ba6d0d3197de38f82c7c5ede
--- /dev/null
+++ b/backend/internal/payment/provider/alipay.go
@@ -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)
+)
diff --git a/backend/internal/payment/provider/alipay_test.go b/backend/internal/payment/provider/alipay_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..1b9d66ba9e51fdcef1aae01400541d573f7c4ff9
--- /dev/null
+++ b/backend/internal/payment/provider/alipay_test.go
@@ -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")
+ }
+ })
+ }
+}
diff --git a/backend/internal/payment/provider/easypay.go b/backend/internal/payment/provider/easypay.go
new file mode 100644
index 0000000000000000000000000000000000000000..e33a567d0398b240a26144f3f1dd44836748d23d
--- /dev/null
+++ b/backend/internal/payment/provider/easypay.go
@@ -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))
+}
diff --git a/backend/internal/payment/provider/easypay_sign_test.go b/backend/internal/payment/provider/easypay_sign_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..146a6fa1afd7aea5649cd9d110edcce86dbbdbb0
--- /dev/null
+++ b/backend/internal/payment/provider/easypay_sign_test.go
@@ -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")
+ }
+}
diff --git a/backend/internal/payment/provider/factory.go b/backend/internal/payment/provider/factory.go
new file mode 100644
index 0000000000000000000000000000000000000000..0adbd2676e6dc153b7ce1f86fa382c15427d9955
--- /dev/null
+++ b/backend/internal/payment/provider/factory.go
@@ -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)
+ }
+}
diff --git a/backend/internal/payment/provider/stripe.go b/backend/internal/payment/provider/stripe.go
new file mode 100644
index 0000000000000000000000000000000000000000..15359d45baf374ad468163a7688698ef8700c744
--- /dev/null
+++ b/backend/internal/payment/provider/stripe.go
@@ -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)
+)
diff --git a/backend/internal/payment/provider/wxpay.go b/backend/internal/payment/provider/wxpay.go
new file mode 100644
index 0000000000000000000000000000000000000000..14e51cd206bc2e6ca732bbd6146964f8a120724c
--- /dev/null
+++ b/backend/internal/payment/provider/wxpay.go
@@ -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)
+)
diff --git a/backend/internal/payment/provider/wxpay_test.go b/backend/internal/payment/provider/wxpay_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..b8b99537457f18701efdec12fc7cf7b06626b328
--- /dev/null
+++ b/backend/internal/payment/provider/wxpay_test.go
@@ -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")
+ }
+ })
+ }
+}
diff --git a/backend/internal/payment/registry.go b/backend/internal/payment/registry.go
new file mode 100644
index 0000000000000000000000000000000000000000..259eb4bb3357b54c08f4c59b37260409ef2d4bfc
--- /dev/null
+++ b/backend/internal/payment/registry.go
@@ -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)
+}
diff --git a/backend/internal/payment/registry_test.go b/backend/internal/payment/registry_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..9684945c761093f8e505b35ef04abdbca0f0ea91
--- /dev/null
+++ b/backend/internal/payment/registry_test.go
@@ -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))
+ }
+}
diff --git a/backend/internal/payment/types.go b/backend/internal/payment/types.go
new file mode 100644
index 0000000000000000000000000000000000000000..c413d8f3ee249fa6792ca6f2a434cdf58fada17f
--- /dev/null
+++ b/backend/internal/payment/types.go
@@ -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
+}
diff --git a/backend/internal/payment/wire.go b/backend/internal/payment/wire.go
new file mode 100644
index 0000000000000000000000000000000000000000..9717465d8e87f55ab98b1bd1862e8566c4dd3211
--- /dev/null
+++ b/backend/internal/payment/wire.go
@@ -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)),
+)
diff --git a/backend/internal/pkg/apicompat/types.go b/backend/internal/pkg/apicompat/types.go
index b724a5ed96d8001de09384910fa7ee5172330401..b383f8671aa78798cfbdd7cc4e2be6b60da5ba4a 100644
--- a/backend/internal/pkg/apicompat/types.go
+++ b/backend/internal/pkg/apicompat/types.go
@@ -28,7 +28,7 @@ type AnthropicRequest struct {
// AnthropicOutputConfig controls output generation parameters.
type AnthropicOutputConfig struct {
- Effort string `json:"effort,omitempty"` // "low" | "medium" | "high"
+ Effort string `json:"effort,omitempty"` // "low" | "medium" | "high" | "max"
}
// AnthropicThinking configures extended thinking in the Anthropic API.
@@ -167,7 +167,7 @@ type ResponsesRequest struct {
// ResponsesReasoning configures reasoning effort in the Responses API.
type ResponsesReasoning struct {
- Effort string `json:"effort"` // "low" | "medium" | "high"
+ Effort string `json:"effort"` // "low" | "medium" | "high" | "xhigh"
Summary string `json:"summary,omitempty"` // "auto" | "concise" | "detailed"
}
@@ -345,7 +345,7 @@ type ChatCompletionsRequest struct {
StreamOptions *ChatStreamOptions `json:"stream_options,omitempty"`
Tools []ChatTool `json:"tools,omitempty"`
ToolChoice json.RawMessage `json:"tool_choice,omitempty"`
- ReasoningEffort string `json:"reasoning_effort,omitempty"` // "low" | "medium" | "high"
+ ReasoningEffort string `json:"reasoning_effort,omitempty"` // "low" | "medium" | "high" | "xhigh"
ServiceTier string `json:"service_tier,omitempty"`
Stop json.RawMessage `json:"stop,omitempty"` // string or []string
diff --git a/backend/internal/pkg/pagination/pagination.go b/backend/internal/pkg/pagination/pagination.go
index c162588ae647d9ab4ce468439ea15c30e9db61b8..ce8e74b8cea4cc9a509a138b2ab395eb1eba14d9 100644
--- a/backend/internal/pkg/pagination/pagination.go
+++ b/backend/internal/pkg/pagination/pagination.go
@@ -1,10 +1,19 @@
// Package pagination provides types and helpers for paginated responses.
package pagination
+import "strings"
+
+const (
+ SortOrderAsc = "asc"
+ SortOrderDesc = "desc"
+)
+
// PaginationParams 分页参数
type PaginationParams struct {
- Page int
- PageSize int
+ Page int
+ PageSize int
+ SortBy string
+ SortOrder string
}
// PaginationResult 分页结果
@@ -18,8 +27,9 @@ type PaginationResult struct {
// DefaultPagination 默认分页参数
func DefaultPagination() PaginationParams {
return PaginationParams{
- Page: 1,
- PageSize: 20,
+ Page: 1,
+ PageSize: 20,
+ SortOrder: SortOrderDesc,
}
}
@@ -36,8 +46,32 @@ func (p PaginationParams) Limit() int {
if p.PageSize < 1 {
return 20
}
- if p.PageSize > 100 {
- return 100
+ if p.PageSize > 1000 {
+ return 1000
}
return p.PageSize
}
+
+// NormalizeSortOrder normalizes sort order to asc/desc and falls back to defaultOrder.
+func NormalizeSortOrder(order string, defaultOrder string) string {
+ switch strings.ToLower(strings.TrimSpace(defaultOrder)) {
+ case SortOrderAsc:
+ defaultOrder = SortOrderAsc
+ default:
+ defaultOrder = SortOrderDesc
+ }
+
+ switch strings.ToLower(strings.TrimSpace(order)) {
+ case SortOrderAsc:
+ return SortOrderAsc
+ case SortOrderDesc:
+ return SortOrderDesc
+ default:
+ return defaultOrder
+ }
+}
+
+// NormalizedSortOrder returns the normalized sort order using defaultOrder as fallback.
+func (p PaginationParams) NormalizedSortOrder(defaultOrder string) string {
+ return NormalizeSortOrder(p.SortOrder, defaultOrder)
+}
diff --git a/backend/internal/pkg/pagination/pagination_test.go b/backend/internal/pkg/pagination/pagination_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..9a3b069d90940c4173c2ae39088039d4cce8b893
--- /dev/null
+++ b/backend/internal/pkg/pagination/pagination_test.go
@@ -0,0 +1,71 @@
+package pagination
+
+import "testing"
+
+func TestNormalizeSortOrder(t *testing.T) {
+ t.Parallel()
+
+ tests := []struct {
+ name string
+ input string
+ defaultOrder string
+ want string
+ }{
+ {name: "asc", input: "asc", defaultOrder: "desc", want: "asc"},
+ {name: "uppercase asc", input: "ASC", defaultOrder: "desc", want: "asc"},
+ {name: "desc", input: "desc", defaultOrder: "asc", want: "desc"},
+ {name: "trim spaces", input: " desc ", defaultOrder: "asc", want: "desc"},
+ {name: "invalid falls back", input: "sideways", defaultOrder: "asc", want: "asc"},
+ {name: "empty falls back", input: "", defaultOrder: "desc", want: "desc"},
+ {name: "invalid default falls back to desc", input: "", defaultOrder: "wat", want: "desc"},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ t.Parallel()
+ if got := NormalizeSortOrder(tt.input, tt.defaultOrder); got != tt.want {
+ t.Fatalf("NormalizeSortOrder(%q, %q) = %q, want %q", tt.input, tt.defaultOrder, got, tt.want)
+ }
+ })
+ }
+}
+
+func TestPaginationParamsNormalizedSortOrder(t *testing.T) {
+ t.Parallel()
+
+ params := PaginationParams{SortOrder: "ASC"}
+ if got := params.NormalizedSortOrder("desc"); got != "asc" {
+ t.Fatalf("NormalizedSortOrder = %q, want asc", got)
+ }
+
+ params = PaginationParams{SortOrder: "bad"}
+ if got := params.NormalizedSortOrder("asc"); got != "asc" {
+ t.Fatalf("NormalizedSortOrder invalid fallback = %q, want asc", got)
+ }
+}
+
+func TestPaginationParamsLimit(t *testing.T) {
+ t.Parallel()
+
+ tests := []struct {
+ name string
+ pageSize int
+ want int
+ }{
+ {name: "non-positive falls back to default", pageSize: 0, want: 20},
+ {name: "negative falls back to default", pageSize: -1, want: 20},
+ {name: "normal value keeps", pageSize: 50, want: 50},
+ {name: "max value keeps", pageSize: 1000, want: 1000},
+ {name: "beyond max clamps to 1000", pageSize: 1500, want: 1000},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ t.Parallel()
+ p := PaginationParams{PageSize: tt.pageSize}
+ if got := p.Limit(); got != tt.want {
+ t.Fatalf("Limit() for PageSize=%d = %d, want %d", tt.pageSize, got, tt.want)
+ }
+ })
+ }
+}
diff --git a/backend/internal/repository/account_repo.go b/backend/internal/repository/account_repo.go
index 144987154f98f3a73b451ee76cf7c400b4ecb5c9..24115c33d74bbdde478b0d28e9feefb93f9dbe58 100644
--- a/backend/internal/repository/account_repo.go
+++ b/backend/internal/repository/account_repo.go
@@ -471,21 +471,58 @@ func (r *accountRepository) ListWithFilters(ctx context.Context, params paginati
case service.StatusActive:
q = q.Where(
dbaccount.StatusEQ(status),
+ dbaccount.SchedulableEQ(true),
dbaccount.Or(
dbaccount.RateLimitResetAtIsNil(),
dbaccount.RateLimitResetAtLTE(time.Now()),
),
+ dbpredicate.Account(func(s *entsql.Selector) {
+ col := s.C("temp_unschedulable_until")
+ s.Where(entsql.Or(
+ entsql.IsNull(col),
+ entsql.LTE(col, entsql.Expr("NOW()")),
+ ))
+ }),
)
case "rate_limited":
- q = q.Where(dbaccount.RateLimitResetAtGT(time.Now()))
+ q = q.Where(
+ dbaccount.StatusEQ(service.StatusActive),
+ dbaccount.RateLimitResetAtGT(time.Now()),
+ dbpredicate.Account(func(s *entsql.Selector) {
+ col := s.C("temp_unschedulable_until")
+ s.Where(entsql.Or(
+ entsql.IsNull(col),
+ entsql.LTE(col, entsql.Expr("NOW()")),
+ ))
+ }),
+ )
case "temp_unschedulable":
- q = q.Where(dbpredicate.Account(func(s *entsql.Selector) {
- col := s.C("temp_unschedulable_until")
- s.Where(entsql.And(
- entsql.Not(entsql.IsNull(col)),
- entsql.GT(col, entsql.Expr("NOW()")),
- ))
- }))
+ q = q.Where(
+ dbaccount.StatusEQ(service.StatusActive),
+ dbpredicate.Account(func(s *entsql.Selector) {
+ col := s.C("temp_unschedulable_until")
+ s.Where(entsql.And(
+ entsql.Not(entsql.IsNull(col)),
+ entsql.GT(col, entsql.Expr("NOW()")),
+ ))
+ }),
+ )
+ case "unschedulable":
+ q = q.Where(
+ dbaccount.StatusEQ(service.StatusActive),
+ dbaccount.SchedulableEQ(false),
+ dbaccount.Or(
+ dbaccount.RateLimitResetAtIsNil(),
+ dbaccount.RateLimitResetAtLTE(time.Now()),
+ ),
+ dbpredicate.Account(func(s *entsql.Selector) {
+ col := s.C("temp_unschedulable_until")
+ s.Where(entsql.Or(
+ entsql.IsNull(col),
+ entsql.LTE(col, entsql.Expr("NOW()")),
+ ))
+ }),
+ )
default:
q = q.Where(dbaccount.StatusEQ(status))
}
@@ -518,11 +555,14 @@ func (r *accountRepository) ListWithFilters(ctx context.Context, params paginati
return nil, nil, err
}
- accounts, err := q.
+ accountsQuery := q.
Offset(params.Offset()).
- Limit(params.Limit()).
- Order(dbent.Desc(dbaccount.FieldID)).
- All(ctx)
+ Limit(params.Limit())
+ for _, order := range accountListOrder(params) {
+ accountsQuery = accountsQuery.Order(order)
+ }
+
+ accounts, err := accountsQuery.All(ctx)
if err != nil {
return nil, nil, err
}
@@ -534,6 +574,50 @@ func (r *accountRepository) ListWithFilters(ctx context.Context, params paginati
return outAccounts, paginationResultFromTotal(int64(total), params), nil
}
+func accountListOrder(params pagination.PaginationParams) []func(*entsql.Selector) {
+ sortBy := strings.ToLower(strings.TrimSpace(params.SortBy))
+ sortOrder := params.NormalizedSortOrder(pagination.SortOrderAsc)
+
+ field := dbaccount.FieldName
+ defaultOrder := true
+ switch sortBy {
+ case "", "name":
+ field = dbaccount.FieldName
+ case "id":
+ field = dbaccount.FieldID
+ defaultOrder = false
+ case "status":
+ field = dbaccount.FieldStatus
+ defaultOrder = false
+ case "schedulable":
+ field = dbaccount.FieldSchedulable
+ defaultOrder = false
+ case "priority":
+ field = dbaccount.FieldPriority
+ defaultOrder = false
+ case "rate_multiplier":
+ field = dbaccount.FieldRateMultiplier
+ defaultOrder = false
+ case "last_used_at":
+ field = dbaccount.FieldLastUsedAt
+ defaultOrder = false
+ case "expires_at":
+ field = dbaccount.FieldExpiresAt
+ defaultOrder = false
+ case "created_at":
+ field = dbaccount.FieldCreatedAt
+ defaultOrder = false
+ }
+
+ if sortOrder == pagination.SortOrderDesc {
+ return []func(*entsql.Selector){dbent.Desc(field), dbent.Desc(dbaccount.FieldID)}
+ }
+ if defaultOrder {
+ return []func(*entsql.Selector){dbent.Asc(dbaccount.FieldName), dbent.Asc(dbaccount.FieldID)}
+ }
+ return []func(*entsql.Selector){dbent.Asc(field), dbent.Asc(dbaccount.FieldID)}
+}
+
func (r *accountRepository) ListByGroup(ctx context.Context, groupID int64) ([]service.Account, error) {
accounts, err := r.queryAccountsByGroup(ctx, groupID, accountGroupQueryOptions{
status: service.StatusActive,
diff --git a/backend/internal/repository/account_repo_integration_test.go b/backend/internal/repository/account_repo_integration_test.go
index f3e3f7452f9eac9d922573acd6e646ec514b8c81..b249bb61b7d30768e0b212b46daa96abae00a07d 100644
--- a/backend/internal/repository/account_repo_integration_test.go
+++ b/backend/internal/repository/account_repo_integration_test.go
@@ -256,7 +256,7 @@ func (s *AccountRepoSuite) TestListWithFilters() {
},
},
{
- name: "filter_by_status_active_excludes_rate_limited",
+ name: "filter_by_status_active_excludes_runtime_blocked_accounts",
setup: func(client *dbent.Client) {
mustCreateAccount(s.T(), client, &service.Account{Name: "active-normal", Status: service.StatusActive})
rateLimited := mustCreateAccount(s.T(), client, &service.Account{Name: "active-rate-limited", Status: service.StatusActive})
@@ -264,6 +264,16 @@ func (s *AccountRepoSuite) TestListWithFilters() {
SetRateLimitResetAt(time.Now().Add(10 * time.Minute)).
Exec(context.Background())
s.Require().NoError(err)
+ tempUnsched := mustCreateAccount(s.T(), client, &service.Account{Name: "active-temp-unsched", Status: service.StatusActive})
+ err = client.Account.UpdateOneID(tempUnsched.ID).
+ SetTempUnschedulableUntil(time.Now().Add(15 * time.Minute)).
+ Exec(context.Background())
+ s.Require().NoError(err)
+ unsched := mustCreateAccount(s.T(), client, &service.Account{Name: "active-unsched", Status: service.StatusActive})
+ err = client.Account.UpdateOneID(unsched.ID).
+ SetSchedulable(false).
+ Exec(context.Background())
+ s.Require().NoError(err)
},
status: service.StatusActive,
wantCount: 1,
@@ -271,6 +281,75 @@ func (s *AccountRepoSuite) TestListWithFilters() {
s.Require().Equal("active-normal", accounts[0].Name)
},
},
+ {
+ name: "filter_by_status_unschedulable_excludes_rate_limited_and_temp_unschedulable",
+ setup: func(client *dbent.Client) {
+ mustCreateAccount(s.T(), client, &service.Account{Name: "active-normal", Status: service.StatusActive, Schedulable: true})
+ unsched := mustCreateAccount(s.T(), client, &service.Account{Name: "active-unsched", Status: service.StatusActive})
+ err := client.Account.UpdateOneID(unsched.ID).
+ SetSchedulable(false).
+ Exec(context.Background())
+ s.Require().NoError(err)
+ rateLimited := mustCreateAccount(s.T(), client, &service.Account{Name: "active-rate-limited", Status: service.StatusActive})
+ err = client.Account.UpdateOneID(rateLimited.ID).
+ SetSchedulable(false).
+ SetRateLimitResetAt(time.Now().Add(10 * time.Minute)).
+ Exec(context.Background())
+ s.Require().NoError(err)
+ tempUnsched := mustCreateAccount(s.T(), client, &service.Account{Name: "active-temp-unsched", Status: service.StatusActive})
+ err = client.Account.UpdateOneID(tempUnsched.ID).
+ SetSchedulable(false).
+ SetTempUnschedulableUntil(time.Now().Add(15 * time.Minute)).
+ Exec(context.Background())
+ s.Require().NoError(err)
+ },
+ status: "unschedulable",
+ wantCount: 1,
+ validate: func(accounts []service.Account) {
+ s.Require().Equal("active-unsched", accounts[0].Name)
+ },
+ },
+ {
+ name: "filter_by_status_rate_limited_excludes_temp_unschedulable",
+ setup: func(client *dbent.Client) {
+ rateLimited := mustCreateAccount(s.T(), client, &service.Account{Name: "active-rate-limited", Status: service.StatusActive})
+ err := client.Account.UpdateOneID(rateLimited.ID).
+ SetRateLimitResetAt(time.Now().Add(10 * time.Minute)).
+ Exec(context.Background())
+ s.Require().NoError(err)
+ tempUnsched := mustCreateAccount(s.T(), client, &service.Account{Name: "active-temp-unsched", Status: service.StatusActive})
+ err = client.Account.UpdateOneID(tempUnsched.ID).
+ SetRateLimitResetAt(time.Now().Add(20 * time.Minute)).
+ SetTempUnschedulableUntil(time.Now().Add(15 * time.Minute)).
+ Exec(context.Background())
+ s.Require().NoError(err)
+ },
+ status: "rate_limited",
+ wantCount: 1,
+ validate: func(accounts []service.Account) {
+ s.Require().Equal("active-rate-limited", accounts[0].Name)
+ },
+ },
+ {
+ name: "filter_by_status_temp_unschedulable_excludes_manually_unschedulable",
+ setup: func(client *dbent.Client) {
+ tempUnsched := mustCreateAccount(s.T(), client, &service.Account{Name: "active-temp-unsched", Status: service.StatusActive, Schedulable: true})
+ err := client.Account.UpdateOneID(tempUnsched.ID).
+ SetTempUnschedulableUntil(time.Now().Add(15 * time.Minute)).
+ Exec(context.Background())
+ s.Require().NoError(err)
+ unsched := mustCreateAccount(s.T(), client, &service.Account{Name: "active-unsched", Status: service.StatusActive})
+ err = client.Account.UpdateOneID(unsched.ID).
+ SetSchedulable(false).
+ Exec(context.Background())
+ s.Require().NoError(err)
+ },
+ status: "temp_unschedulable",
+ wantCount: 1,
+ validate: func(accounts []service.Account) {
+ s.Require().Equal("active-temp-unsched", accounts[0].Name)
+ },
+ },
{
name: "filter_by_search",
setup: func(client *dbent.Client) {
diff --git a/backend/internal/repository/account_repo_sort_integration_test.go b/backend/internal/repository/account_repo_sort_integration_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..098dde7bbdb5a05a4c4c58546133cebb7485e002
--- /dev/null
+++ b/backend/internal/repository/account_repo_sort_integration_test.go
@@ -0,0 +1,35 @@
+//go:build integration
+
+package repository
+
+import (
+ "github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
+ "github.com/Wei-Shaw/sub2api/internal/service"
+)
+
+func (s *AccountRepoSuite) TestList_DefaultSortByNameAsc() {
+ mustCreateAccount(s.T(), s.client, &service.Account{Name: "z-account"})
+ mustCreateAccount(s.T(), s.client, &service.Account{Name: "a-account"})
+
+ accounts, _, err := s.repo.List(s.ctx, pagination.PaginationParams{Page: 1, PageSize: 10})
+ s.Require().NoError(err)
+ s.Require().Len(accounts, 2)
+ s.Require().Equal("a-account", accounts[0].Name)
+ s.Require().Equal("z-account", accounts[1].Name)
+}
+
+func (s *AccountRepoSuite) TestListWithFilters_SortByPriorityDesc() {
+ mustCreateAccount(s.T(), s.client, &service.Account{Name: "low-priority", Priority: 10})
+ mustCreateAccount(s.T(), s.client, &service.Account{Name: "high-priority", Priority: 90})
+
+ accounts, _, err := s.repo.ListWithFilters(s.ctx, pagination.PaginationParams{
+ Page: 1,
+ PageSize: 10,
+ SortBy: "priority",
+ SortOrder: "desc",
+ }, "", "", "", "", 0, "")
+ s.Require().NoError(err)
+ s.Require().Len(accounts, 2)
+ s.Require().Equal("high-priority", accounts[0].Name)
+ s.Require().Equal("low-priority", accounts[1].Name)
+}
diff --git a/backend/internal/repository/announcement_repo.go b/backend/internal/repository/announcement_repo.go
index 53dc335f8c867f83b4f9141c6a11e51f9b2b19bf..afe1fb25c9000ca31dcb6e6e72caa35c02d5a8f1 100644
--- a/backend/internal/repository/announcement_repo.go
+++ b/backend/internal/repository/announcement_repo.go
@@ -2,12 +2,15 @@ package repository
import (
"context"
+ "strings"
"time"
dbent "github.com/Wei-Shaw/sub2api/ent"
"github.com/Wei-Shaw/sub2api/ent/announcement"
"github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
"github.com/Wei-Shaw/sub2api/internal/service"
+
+ entsql "entgo.io/ent/dialect/sql"
)
type announcementRepository struct {
@@ -128,11 +131,14 @@ func (r *announcementRepository) List(
return nil, nil, err
}
- items, err := q.
+ itemsQuery := q.
Offset(params.Offset()).
- Limit(params.Limit()).
- Order(dbent.Desc(announcement.FieldID)).
- All(ctx)
+ Limit(params.Limit())
+ for _, order := range announcementListOrders(params) {
+ itemsQuery = itemsQuery.Order(order)
+ }
+
+ items, err := itemsQuery.All(ctx)
if err != nil {
return nil, nil, err
}
@@ -141,6 +147,56 @@ func (r *announcementRepository) List(
return out, paginationResultFromTotal(int64(total), params), nil
}
+func announcementListOrder(params pagination.PaginationParams) (string, string) {
+ sortBy := strings.ToLower(strings.TrimSpace(params.SortBy))
+ sortOrder := params.NormalizedSortOrder(pagination.SortOrderDesc)
+
+ switch sortBy {
+ case "title":
+ return announcement.FieldTitle, sortOrder
+ case "status":
+ return announcement.FieldStatus, sortOrder
+ case "notify_mode":
+ return announcement.FieldNotifyMode, sortOrder
+ case "starts_at":
+ return announcement.FieldStartsAt, sortOrder
+ case "ends_at":
+ return announcement.FieldEndsAt, sortOrder
+ case "id":
+ return announcement.FieldID, sortOrder
+ case "", "created_at":
+ return announcement.FieldCreatedAt, sortOrder
+ default:
+ return announcement.FieldCreatedAt, pagination.SortOrderDesc
+ }
+}
+
+func announcementListOrders(params pagination.PaginationParams) []func(*entsql.Selector) {
+ field, sortOrder := announcementListOrder(params)
+
+ if sortOrder == pagination.SortOrderAsc {
+ if field == announcement.FieldID {
+ return []func(*entsql.Selector){
+ dbent.Asc(field),
+ }
+ }
+ return []func(*entsql.Selector){
+ dbent.Asc(field),
+ dbent.Asc(announcement.FieldID),
+ }
+ }
+
+ if field == announcement.FieldID {
+ return []func(*entsql.Selector){
+ dbent.Desc(field),
+ }
+ }
+ return []func(*entsql.Selector){
+ dbent.Desc(field),
+ dbent.Desc(announcement.FieldID),
+ }
+}
+
func (r *announcementRepository) ListActive(ctx context.Context, now time.Time) ([]service.Announcement, error) {
q := r.client.Announcement.Query().
Where(
diff --git a/backend/internal/repository/announcement_repo_sort_test.go b/backend/internal/repository/announcement_repo_sort_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..e47f98dcfc5e3c5c53eaa7c32ad2211d74cc4286
--- /dev/null
+++ b/backend/internal/repository/announcement_repo_sort_test.go
@@ -0,0 +1,63 @@
+package repository
+
+import (
+ "testing"
+
+ "github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
+)
+
+func TestAnnouncementListOrder(t *testing.T) {
+ t.Parallel()
+
+ tests := []struct {
+ name string
+ params pagination.PaginationParams
+ wantBy string
+ want string
+ }{
+ {
+ name: "default created_at desc",
+ params: pagination.PaginationParams{},
+ wantBy: "created_at",
+ want: "desc",
+ },
+ {
+ name: "title asc",
+ params: pagination.PaginationParams{
+ SortBy: "title",
+ SortOrder: "ASC",
+ },
+ wantBy: "title",
+ want: "asc",
+ },
+ {
+ name: "status desc",
+ params: pagination.PaginationParams{
+ SortBy: "status",
+ SortOrder: "desc",
+ },
+ wantBy: "status",
+ want: "desc",
+ },
+ {
+ name: "invalid falls back",
+ params: pagination.PaginationParams{
+ SortBy: "sideways",
+ SortOrder: "wat",
+ },
+ wantBy: "created_at",
+ want: "desc",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ t.Parallel()
+
+ gotBy, gotOrder := announcementListOrder(tt.params)
+ if gotBy != tt.wantBy || gotOrder != tt.want {
+ t.Fatalf("announcementListOrder(%+v) = (%q, %q), want (%q, %q)", tt.params, gotBy, gotOrder, tt.wantBy, tt.want)
+ }
+ })
+ }
+}
diff --git a/backend/internal/repository/api_key_repo.go b/backend/internal/repository/api_key_repo.go
index b3b12e8113d2e3e80b49cf4cd90dacc7fca3d5cb..7fd988550f36625cefd9c3ae51a7518fd3852ae5 100644
--- a/backend/internal/repository/api_key_repo.go
+++ b/backend/internal/repository/api_key_repo.go
@@ -4,6 +4,7 @@ import (
"context"
"database/sql"
"fmt"
+ "strings"
"time"
dbent "github.com/Wei-Shaw/sub2api/ent"
@@ -14,6 +15,8 @@ import (
"github.com/Wei-Shaw/sub2api/internal/service"
"github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
+
+ entsql "entgo.io/ent/dialect/sql"
)
type apiKeyRepository struct {
@@ -164,6 +167,7 @@ func (r *apiKeyRepository) GetByKeyForAuth(ctx context.Context, key string) (*se
group.FieldSupportedModelScopes,
group.FieldAllowMessagesDispatch,
group.FieldDefaultMappedModel,
+ group.FieldMessagesDispatchModelConfig,
)
}).
Only(ctx)
@@ -309,12 +313,15 @@ func (r *apiKeyRepository) ListByUserID(ctx context.Context, userID int64, param
return nil, nil, err
}
- keys, err := q.
+ keysQuery := q.
WithGroup().
Offset(params.Offset()).
- Limit(params.Limit()).
- Order(dbent.Desc(apikey.FieldID)).
- All(ctx)
+ Limit(params.Limit())
+ for _, order := range apiKeyListOrder(params) {
+ keysQuery = keysQuery.Order(order)
+ }
+
+ keys, err := keysQuery.All(ctx)
if err != nil {
return nil, nil, err
}
@@ -359,12 +366,15 @@ func (r *apiKeyRepository) ListByGroupID(ctx context.Context, groupID int64, par
return nil, nil, err
}
- keys, err := q.
+ keysQuery := q.
WithUser().
Offset(params.Offset()).
- Limit(params.Limit()).
- Order(dbent.Desc(apikey.FieldID)).
- All(ctx)
+ Limit(params.Limit())
+ for _, order := range apiKeyListOrder(params) {
+ keysQuery = keysQuery.Order(order)
+ }
+
+ keys, err := keysQuery.All(ctx)
if err != nil {
return nil, nil, err
}
@@ -377,6 +387,32 @@ func (r *apiKeyRepository) ListByGroupID(ctx context.Context, groupID int64, par
return outKeys, paginationResultFromTotal(int64(total), params), nil
}
+func apiKeyListOrder(params pagination.PaginationParams) []func(*entsql.Selector) {
+ sortBy := strings.ToLower(strings.TrimSpace(params.SortBy))
+ sortOrder := params.NormalizedSortOrder(pagination.SortOrderDesc)
+
+ var field string
+ switch sortBy {
+ case "name":
+ field = apikey.FieldName
+ case "status":
+ field = apikey.FieldStatus
+ case "expires_at":
+ field = apikey.FieldExpiresAt
+ case "last_used_at":
+ field = apikey.FieldLastUsedAt
+ case "created_at":
+ field = apikey.FieldCreatedAt
+ default:
+ field = apikey.FieldID
+ }
+
+ if sortOrder == pagination.SortOrderAsc {
+ return []func(*entsql.Selector){dbent.Asc(field), dbent.Asc(apikey.FieldID)}
+ }
+ return []func(*entsql.Selector){dbent.Desc(field), dbent.Desc(apikey.FieldID)}
+}
+
// SearchAPIKeys searches API keys by user ID and/or keyword (name)
func (r *apiKeyRepository) SearchAPIKeys(ctx context.Context, userID int64, keyword string, limit int) ([]service.APIKey, error) {
q := r.activeQuery()
@@ -654,6 +690,7 @@ func groupEntityToService(g *dbent.Group) *service.Group {
RequireOAuthOnly: g.RequireOauthOnly,
RequirePrivacySet: g.RequirePrivacySet,
DefaultMappedModel: g.DefaultMappedModel,
+ MessagesDispatchModelConfig: g.MessagesDispatchModelConfig,
CreatedAt: g.CreatedAt,
UpdatedAt: g.UpdatedAt,
}
diff --git a/backend/internal/repository/api_key_repo_integration_test.go b/backend/internal/repository/api_key_repo_integration_test.go
index 7d5c18260ba25bc5899ec24d72db18e313e522d4..e926ed8602843311d001d37c8a8bc3b5c4233bb5 100644
--- a/backend/internal/repository/api_key_repo_integration_test.go
+++ b/backend/internal/repository/api_key_repo_integration_test.go
@@ -86,6 +86,45 @@ func (s *APIKeyRepoSuite) TestGetByKey_NotFound() {
s.Require().Error(err, "expected error for non-existent key")
}
+func (s *APIKeyRepoSuite) TestGetByKeyForAuth_PreservesMessagesDispatchModelConfig() {
+ user := s.mustCreateUser("getbykey-auth-dispatch@test.com")
+ group, err := s.client.Group.Create().
+ SetName("g-auth-dispatch").
+ SetPlatform(service.PlatformOpenAI).
+ SetStatus(service.StatusActive).
+ SetSubscriptionType(service.SubscriptionTypeStandard).
+ SetRateMultiplier(1).
+ SetAllowMessagesDispatch(true).
+ SetDefaultMappedModel("gpt-5.4").
+ SetMessagesDispatchModelConfig(service.OpenAIMessagesDispatchModelConfig{
+ OpusMappedModel: "gpt-5.4-nano",
+ SonnetMappedModel: "gpt-5.3-codex",
+ HaikuMappedModel: "gpt-5.4-mini",
+ ExactModelMappings: map[string]string{
+ "claude-sonnet-4.5": "gpt-5.4-nano",
+ },
+ }).
+ Save(s.ctx)
+ s.Require().NoError(err)
+
+ key := &service.APIKey{
+ UserID: user.ID,
+ Key: "sk-getbykey-auth-dispatch",
+ Name: "Dispatch Key",
+ GroupID: &group.ID,
+ Status: service.StatusActive,
+ }
+ s.Require().NoError(s.repo.Create(s.ctx, key))
+
+ got, err := s.repo.GetByKeyForAuth(s.ctx, key.Key)
+ s.Require().NoError(err)
+ s.Require().NotNil(got.Group)
+ s.Require().True(got.Group.AllowMessagesDispatch)
+ s.Require().Equal("gpt-5.4", got.Group.DefaultMappedModel)
+ s.Require().Equal("gpt-5.4-nano", got.Group.MessagesDispatchModelConfig.OpusMappedModel)
+ s.Require().Equal("gpt-5.4-nano", got.Group.MessagesDispatchModelConfig.ExactModelMappings["claude-sonnet-4.5"])
+}
+
// --- Update ---
func (s *APIKeyRepoSuite) TestUpdate() {
diff --git a/backend/internal/repository/api_key_repo_messages_dispatch_unit_test.go b/backend/internal/repository/api_key_repo_messages_dispatch_unit_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..aba62ead2ebf7639ca5cb3788c4b1256feb6927e
--- /dev/null
+++ b/backend/internal/repository/api_key_repo_messages_dispatch_unit_test.go
@@ -0,0 +1,74 @@
+package repository
+
+import (
+ "context"
+ "testing"
+
+ dbent "github.com/Wei-Shaw/sub2api/ent"
+ "github.com/Wei-Shaw/sub2api/internal/service"
+ "github.com/stretchr/testify/require"
+)
+
+func TestGroupEntityToService_PreservesMessagesDispatchModelConfig(t *testing.T) {
+ group := &dbent.Group{
+ ID: 1,
+ Name: "openai-dispatch",
+ Platform: service.PlatformOpenAI,
+ Status: service.StatusActive,
+ SubscriptionType: service.SubscriptionTypeStandard,
+ RateMultiplier: 1,
+ AllowMessagesDispatch: true,
+ DefaultMappedModel: "gpt-5.4",
+ MessagesDispatchModelConfig: service.OpenAIMessagesDispatchModelConfig{
+ OpusMappedModel: "gpt-5.4-nano",
+ SonnetMappedModel: "gpt-5.3-codex",
+ HaikuMappedModel: "gpt-5.4-mini",
+ ExactModelMappings: map[string]string{
+ "claude-sonnet-4.5": "gpt-5.4-nano",
+ },
+ },
+ }
+
+ got := groupEntityToService(group)
+ require.NotNil(t, got)
+ require.Equal(t, group.MessagesDispatchModelConfig, got.MessagesDispatchModelConfig)
+}
+
+func TestAPIKeyRepository_GetByKeyForAuth_PreservesMessagesDispatchModelConfig_SQLite(t *testing.T) {
+ repo, client := newAPIKeyRepoSQLite(t)
+ ctx := context.Background()
+ user := mustCreateAPIKeyRepoUser(t, ctx, client, "getbykey-auth-dispatch-unit@test.com")
+
+ group, err := client.Group.Create().
+ SetName("g-auth-dispatch-unit").
+ SetPlatform(service.PlatformOpenAI).
+ SetStatus(service.StatusActive).
+ SetSubscriptionType(service.SubscriptionTypeStandard).
+ SetRateMultiplier(1).
+ SetAllowMessagesDispatch(true).
+ SetDefaultMappedModel("gpt-5.4").
+ SetMessagesDispatchModelConfig(service.OpenAIMessagesDispatchModelConfig{
+ OpusMappedModel: "gpt-5.4-nano",
+ SonnetMappedModel: "gpt-5.3-codex",
+ HaikuMappedModel: "gpt-5.4-mini",
+ ExactModelMappings: map[string]string{
+ "claude-sonnet-4.5": "gpt-5.4-nano",
+ },
+ }).
+ Save(ctx)
+ require.NoError(t, err)
+
+ key := &service.APIKey{
+ UserID: user.ID,
+ Key: "sk-getbykey-auth-dispatch-unit",
+ Name: "Dispatch Key Unit",
+ GroupID: &group.ID,
+ Status: service.StatusActive,
+ }
+ require.NoError(t, repo.Create(ctx, key))
+
+ got, err := repo.GetByKeyForAuth(ctx, key.Key)
+ require.NoError(t, err)
+ require.NotNil(t, got.Group)
+ require.Equal(t, group.MessagesDispatchModelConfig, got.Group.MessagesDispatchModelConfig)
+}
diff --git a/backend/internal/repository/api_key_repo_sort_integration_test.go b/backend/internal/repository/api_key_repo_sort_integration_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..69812882feb5f577b07ad1f0f56f9ac62c6b7789
--- /dev/null
+++ b/backend/internal/repository/api_key_repo_sort_integration_test.go
@@ -0,0 +1,25 @@
+//go:build integration
+
+package repository
+
+import (
+ "github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
+ "github.com/Wei-Shaw/sub2api/internal/service"
+)
+
+func (s *APIKeyRepoSuite) TestListByUserID_SortByNameAsc() {
+ user := s.mustCreateUser("sort-name@example.com")
+ s.mustCreateApiKey(user.ID, "sk-z", "z-key", nil)
+ s.mustCreateApiKey(user.ID, "sk-a", "a-key", nil)
+
+ keys, _, err := s.repo.ListByUserID(s.ctx, user.ID, pagination.PaginationParams{
+ Page: 1,
+ PageSize: 10,
+ SortBy: "name",
+ SortOrder: "asc",
+ }, service.APIKeyListFilters{})
+ s.Require().NoError(err)
+ s.Require().Len(keys, 2)
+ s.Require().Equal("a-key", keys[0].Name)
+ s.Require().Equal("z-key", keys[1].Name)
+}
diff --git a/backend/internal/repository/channel_repo.go b/backend/internal/repository/channel_repo.go
index 1e2c2e4cf34b93b735aac6eea810a0c70995f4ed..49c2d8d92c349496686fdee208c87b0eb87d2fc6 100644
--- a/backend/internal/repository/channel_repo.go
+++ b/backend/internal/repository/channel_repo.go
@@ -188,8 +188,8 @@ func (r *channelRepository) List(ctx context.Context, params pagination.Paginati
// 查询 channel 列表
dataQuery := fmt.Sprintf(
`SELECT c.id, c.name, c.description, c.status, c.model_mapping, c.billing_model_source, c.restrict_models, c.created_at, c.updated_at
- FROM channels c WHERE %s ORDER BY c.id ASC LIMIT $%d OFFSET $%d`,
- whereClause, argIdx, argIdx+1,
+ FROM channels c WHERE %s ORDER BY %s LIMIT $%d OFFSET $%d`,
+ whereClause, channelListOrderBy(params), argIdx, argIdx+1,
)
args = append(args, pageSize, offset)
@@ -246,6 +246,31 @@ func (r *channelRepository) List(ctx context.Context, params pagination.Paginati
return channels, paginationResult, nil
}
+func channelListOrderBy(params pagination.PaginationParams) string {
+ sortBy := strings.ToLower(strings.TrimSpace(params.SortBy))
+ sortOrder := strings.ToUpper(params.NormalizedSortOrder(pagination.SortOrderAsc))
+
+ var column string
+ switch sortBy {
+ case "":
+ column = "c.id"
+ sortOrder = "ASC"
+ case "id":
+ column = "c.id"
+ case "name":
+ column = "c.name"
+ case "status":
+ column = "c.status"
+ case "created_at":
+ column = "c.created_at"
+ default:
+ column = "c.id"
+ sortOrder = "ASC"
+ }
+
+ return fmt.Sprintf("%s %s, c.id %s", column, sortOrder, sortOrder)
+}
+
func (r *channelRepository) ListAll(ctx context.Context) ([]service.Channel, error) {
rows, err := r.db.QueryContext(ctx,
`SELECT id, name, description, status, model_mapping, billing_model_source, restrict_models, created_at, updated_at FROM channels ORDER BY id`,
diff --git a/backend/internal/repository/channel_repo_test.go b/backend/internal/repository/channel_repo_test.go
index 5a59948d25c43fd76fe40c76fec9f3e4692810f6..e761866d709ac0de690615137c1ae3ba7aeee1b1 100644
--- a/backend/internal/repository/channel_repo_test.go
+++ b/backend/internal/repository/channel_repo_test.go
@@ -8,6 +8,7 @@ import (
"fmt"
"testing"
+ "github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
"github.com/lib/pq"
"github.com/stretchr/testify/require"
)
@@ -225,3 +226,12 @@ func TestIsUniqueViolation(t *testing.T) {
})
}
}
+
+func TestChannelListOrderBy_AllowsDescendingIDSort(t *testing.T) {
+ params := pagination.PaginationParams{
+ SortBy: "id",
+ SortOrder: "desc",
+ }
+
+ require.Equal(t, "c.id DESC, c.id DESC", channelListOrderBy(params))
+}
diff --git a/backend/internal/repository/group_repo.go b/backend/internal/repository/group_repo.go
index a075b586c49ace4762ac08ce69b433ec71bf4200..c17e3365d3ce70e0b0b1482330bc9083a3590eae 100644
--- a/backend/internal/repository/group_repo.go
+++ b/backend/internal/repository/group_repo.go
@@ -5,6 +5,7 @@ import (
"database/sql"
"errors"
"fmt"
+ "sort"
"strings"
dbent "github.com/Wei-Shaw/sub2api/ent"
@@ -14,6 +15,8 @@ import (
"github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
"github.com/Wei-Shaw/sub2api/internal/service"
"github.com/lib/pq"
+
+ entsql "entgo.io/ent/dialect/sql"
)
type sqlExecutor interface {
@@ -40,6 +43,7 @@ func (r *groupRepository) Create(ctx context.Context, groupIn *service.Group) er
SetDescription(groupIn.Description).
SetPlatform(groupIn.Platform).
SetRateMultiplier(groupIn.RateMultiplier).
+ SetSortOrder(groupIn.SortOrder).
SetIsExclusive(groupIn.IsExclusive).
SetStatus(groupIn.Status).
SetSubscriptionType(groupIn.SubscriptionType).
@@ -58,7 +62,8 @@ func (r *groupRepository) Create(ctx context.Context, groupIn *service.Group) er
SetAllowMessagesDispatch(groupIn.AllowMessagesDispatch).
SetRequireOauthOnly(groupIn.RequireOAuthOnly).
SetRequirePrivacySet(groupIn.RequirePrivacySet).
- SetDefaultMappedModel(groupIn.DefaultMappedModel)
+ SetDefaultMappedModel(groupIn.DefaultMappedModel).
+ SetMessagesDispatchModelConfig(groupIn.MessagesDispatchModelConfig)
// 设置模型路由配置
if groupIn.ModelRouting != nil {
@@ -124,7 +129,8 @@ func (r *groupRepository) Update(ctx context.Context, groupIn *service.Group) er
SetAllowMessagesDispatch(groupIn.AllowMessagesDispatch).
SetRequireOauthOnly(groupIn.RequireOAuthOnly).
SetRequirePrivacySet(groupIn.RequirePrivacySet).
- SetDefaultMappedModel(groupIn.DefaultMappedModel)
+ SetDefaultMappedModel(groupIn.DefaultMappedModel).
+ SetMessagesDispatchModelConfig(groupIn.MessagesDispatchModelConfig)
// 显式处理可空字段:nil 需要 clear,非 nil 需要 set。
if groupIn.DailyLimitUSD != nil {
@@ -231,11 +237,18 @@ func (r *groupRepository) ListWithFilters(ctx context.Context, params pagination
return nil, nil, err
}
- groups, err := q.
+ if strings.EqualFold(strings.TrimSpace(params.SortBy), "account_count") {
+ return r.listWithAccountCountSort(ctx, q, params, total)
+ }
+
+ groupsQuery := q.
Offset(params.Offset()).
- Limit(params.Limit()).
- Order(dbent.Asc(group.FieldSortOrder), dbent.Asc(group.FieldID)).
- All(ctx)
+ Limit(params.Limit())
+ for _, order := range groupListOrder(params) {
+ groupsQuery = groupsQuery.Order(order)
+ }
+
+ groups, err := groupsQuery.All(ctx)
if err != nil {
return nil, nil, err
}
@@ -261,6 +274,104 @@ func (r *groupRepository) ListWithFilters(ctx context.Context, params pagination
return outGroups, paginationResultFromTotal(int64(total), params), nil
}
+func (r *groupRepository) listWithAccountCountSort(ctx context.Context, q *dbent.GroupQuery, params pagination.PaginationParams, total int) ([]service.Group, *pagination.PaginationResult, error) {
+ groups, err := q.
+ Order(dbent.Asc(group.FieldSortOrder), dbent.Asc(group.FieldID)).
+ All(ctx)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ groupIDs := make([]int64, 0, len(groups))
+ outGroups := make([]service.Group, 0, len(groups))
+ for i := range groups {
+ g := groupEntityToService(groups[i])
+ outGroups = append(outGroups, *g)
+ groupIDs = append(groupIDs, g.ID)
+ }
+
+ counts, err := r.loadAccountCounts(ctx, groupIDs)
+ if err != nil {
+ return nil, nil, err
+ }
+ for i := range outGroups {
+ c := counts[outGroups[i].ID]
+ outGroups[i].AccountCount = c.Total
+ outGroups[i].ActiveAccountCount = c.Active
+ outGroups[i].RateLimitedAccountCount = c.RateLimited
+ }
+
+ sortOrder := params.NormalizedSortOrder(pagination.SortOrderDesc)
+ sort.SliceStable(outGroups, func(i, j int) bool {
+ if outGroups[i].AccountCount == outGroups[j].AccountCount {
+ if outGroups[i].SortOrder == outGroups[j].SortOrder {
+ return outGroups[i].ID < outGroups[j].ID
+ }
+ return outGroups[i].SortOrder < outGroups[j].SortOrder
+ }
+ if sortOrder == pagination.SortOrderAsc {
+ return outGroups[i].AccountCount < outGroups[j].AccountCount
+ }
+ return outGroups[i].AccountCount > outGroups[j].AccountCount
+ })
+
+ return paginateSlice(outGroups, params), paginationResultFromTotal(int64(total), params), nil
+}
+
+func groupListOrder(params pagination.PaginationParams) []func(*entsql.Selector) {
+ sortBy := strings.ToLower(strings.TrimSpace(params.SortBy))
+ sortOrder := params.NormalizedSortOrder(pagination.SortOrderAsc)
+
+ var field string
+ tieField := group.FieldID
+ defaultOrder := true
+ switch sortBy {
+ case "", "sort_order":
+ field = group.FieldSortOrder
+ case "name":
+ field = group.FieldName
+ defaultOrder = false
+ case "platform":
+ field = group.FieldPlatform
+ defaultOrder = false
+ case "billing_type", "subscription_type":
+ field = group.FieldSubscriptionType
+ defaultOrder = false
+ case "rate_multiplier":
+ field = group.FieldRateMultiplier
+ defaultOrder = false
+ case "is_exclusive":
+ field = group.FieldIsExclusive
+ defaultOrder = false
+ case "status":
+ field = group.FieldStatus
+ defaultOrder = false
+ case "created_at":
+ field = group.FieldCreatedAt
+ defaultOrder = false
+ case "id":
+ field = group.FieldID
+ defaultOrder = false
+ tieField = ""
+ default:
+ field = group.FieldSortOrder
+ }
+
+ if sortOrder == pagination.SortOrderDesc && sortBy != "" {
+ if tieField == "" {
+ return []func(*entsql.Selector){dbent.Desc(field)}
+ }
+ return []func(*entsql.Selector){dbent.Desc(field), dbent.Desc(tieField)}
+ }
+ if defaultOrder {
+ return []func(*entsql.Selector){dbent.Asc(group.FieldSortOrder), dbent.Asc(group.FieldID)}
+ }
+ if tieField == "" {
+ return []func(*entsql.Selector){dbent.Asc(field)}
+ }
+ return []func(*entsql.Selector){dbent.Asc(field), dbent.Asc(tieField)}
+}
+
func (r *groupRepository) ListActive(ctx context.Context) ([]service.Group, error) {
groups, err := r.client.Group.Query().
Where(group.StatusEQ(service.StatusActive)).
diff --git a/backend/internal/repository/group_repo_integration_test.go b/backend/internal/repository/group_repo_integration_test.go
index eccf5ceacbd692e760c53f34fa119b9c3370ad72..f91dae43eb6d9bf8eea97c785e1e9f1d4be54445 100644
--- a/backend/internal/repository/group_repo_integration_test.go
+++ b/backend/internal/repository/group_repo_integration_test.go
@@ -113,6 +113,33 @@ func (s *GroupRepoSuite) TestUpdate() {
s.Require().Equal("updated", got.Name)
}
+func (s *GroupRepoSuite) TestGetByID_PreservesMessagesDispatchModelConfig() {
+ group := &service.Group{
+ Name: "openai-dispatch",
+ Platform: service.PlatformOpenAI,
+ RateMultiplier: 1.0,
+ IsExclusive: false,
+ Status: service.StatusActive,
+ SubscriptionType: service.SubscriptionTypeStandard,
+ AllowMessagesDispatch: true,
+ DefaultMappedModel: "gpt-5.4",
+ MessagesDispatchModelConfig: service.OpenAIMessagesDispatchModelConfig{
+ OpusMappedModel: "gpt-5.4",
+ SonnetMappedModel: "gpt-5.3-codex",
+ HaikuMappedModel: "gpt-5.4-mini",
+ ExactModelMappings: map[string]string{
+ "claude-sonnet-4.5": "gpt-5.4-nano",
+ },
+ },
+ }
+
+ s.Require().NoError(s.repo.Create(s.ctx, group))
+
+ got, err := s.repo.GetByID(s.ctx, group.ID)
+ s.Require().NoError(err)
+ s.Require().Equal(group.MessagesDispatchModelConfig, got.MessagesDispatchModelConfig)
+}
+
func (s *GroupRepoSuite) TestDelete() {
group := &service.Group{
Name: "to-delete",
diff --git a/backend/internal/repository/group_repo_sort_integration_test.go b/backend/internal/repository/group_repo_sort_integration_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..85b2efcc61c0f6893d7034e3e8ed94845bae67c9
--- /dev/null
+++ b/backend/internal/repository/group_repo_sort_integration_test.go
@@ -0,0 +1,50 @@
+//go:build integration
+
+package repository
+
+import (
+ "github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
+ "github.com/Wei-Shaw/sub2api/internal/service"
+)
+
+func (s *GroupRepoSuite) TestList_DefaultSortBySortOrderAsc() {
+ g1 := &service.Group{Name: "g1", Platform: service.PlatformAnthropic, RateMultiplier: 1, Status: service.StatusActive, SubscriptionType: service.SubscriptionTypeStandard, SortOrder: 20}
+ g2 := &service.Group{Name: "g2", Platform: service.PlatformAnthropic, RateMultiplier: 1, Status: service.StatusActive, SubscriptionType: service.SubscriptionTypeStandard, SortOrder: 10}
+ s.Require().NoError(s.repo.Create(s.ctx, g1))
+ s.Require().NoError(s.repo.Create(s.ctx, g2))
+
+ groups, _, err := s.repo.List(s.ctx, pagination.PaginationParams{Page: 1, PageSize: 100})
+ s.Require().NoError(err)
+ s.Require().GreaterOrEqual(len(groups), 2)
+ indexByID := make(map[int64]int, len(groups))
+ for i, g := range groups {
+ indexByID[g.ID] = i
+ }
+ s.Require().Contains(indexByID, g1.ID)
+ s.Require().Contains(indexByID, g2.ID)
+ // g2 has SortOrder=10, g1 has SortOrder=20; ascending means g2 comes first
+ s.Require().Less(indexByID[g2.ID], indexByID[g1.ID])
+}
+
+func (s *GroupRepoSuite) TestList_SortBySortOrderDesc() {
+ g1 := &service.Group{Name: "g1", Platform: service.PlatformAnthropic, RateMultiplier: 1, Status: service.StatusActive, SubscriptionType: service.SubscriptionTypeStandard, SortOrder: 40}
+ g2 := &service.Group{Name: "g2", Platform: service.PlatformAnthropic, RateMultiplier: 1, Status: service.StatusActive, SubscriptionType: service.SubscriptionTypeStandard, SortOrder: 50}
+ s.Require().NoError(s.repo.Create(s.ctx, g1))
+ s.Require().NoError(s.repo.Create(s.ctx, g2))
+
+ groups, _, err := s.repo.List(s.ctx, pagination.PaginationParams{
+ Page: 1,
+ PageSize: 10,
+ SortBy: "sort_order",
+ SortOrder: "desc",
+ })
+ s.Require().NoError(err)
+ s.Require().GreaterOrEqual(len(groups), 2)
+ indexByID := make(map[int64]int, len(groups))
+ for i, group := range groups {
+ indexByID[group.ID] = i
+ }
+ s.Require().Contains(indexByID, g1.ID)
+ s.Require().Contains(indexByID, g2.ID)
+ s.Require().Less(indexByID[g2.ID], indexByID[g1.ID])
+}
diff --git a/backend/internal/repository/integration_harness_test.go b/backend/internal/repository/integration_harness_test.go
index fb9c26c4aabedb3bc08070108880dd8b87fe4bcf..5857fbcb2ac1236c83d50293dbb6f6574e9f7f81 100644
--- a/backend/internal/repository/integration_harness_test.go
+++ b/backend/internal/repository/integration_harness_test.go
@@ -332,6 +332,10 @@ func (h prefixHook) prefixCmd(cmd redisclient.Cmder) {
"hgetall", "hget", "hset", "hdel", "hincrbyfloat", "exists",
"zadd", "zcard", "zrange", "zrangebyscore", "zrem", "zremrangebyscore", "zrevrange", "zrevrangebyscore", "zscore":
prefixOne(1)
+ case "mget":
+ for i := 1; i < len(args); i++ {
+ prefixOne(i)
+ }
case "del", "unlink":
for i := 1; i < len(args); i++ {
prefixOne(i)
diff --git a/backend/internal/repository/pagination.go b/backend/internal/repository/pagination.go
index ff08c34be1e45d327cb6d20af3ab968cf7630b89..87c42a59818da658d4348f8ff4ab2b7e75c40faa 100644
--- a/backend/internal/repository/pagination.go
+++ b/backend/internal/repository/pagination.go
@@ -14,3 +14,22 @@ func paginationResultFromTotal(total int64, params pagination.PaginationParams)
Pages: pages,
}
}
+
+func paginateSlice[T any](items []T, params pagination.PaginationParams) []T {
+ if len(items) == 0 {
+ return []T{}
+ }
+
+ offset := params.Offset()
+ if offset >= len(items) {
+ return []T{}
+ }
+
+ limit := params.Limit()
+ end := offset + limit
+ if end > len(items) {
+ end = len(items)
+ }
+
+ return items[offset:end]
+}
diff --git a/backend/internal/repository/promo_code_repo.go b/backend/internal/repository/promo_code_repo.go
index 95ce687a2a323345d48617c113cba1f31f232cbc..d9c76bb3d49611acba0fe1f1c5c572dac0ef6821 100644
--- a/backend/internal/repository/promo_code_repo.go
+++ b/backend/internal/repository/promo_code_repo.go
@@ -2,12 +2,15 @@ package repository
import (
"context"
+ "strings"
dbent "github.com/Wei-Shaw/sub2api/ent"
"github.com/Wei-Shaw/sub2api/ent/promocode"
"github.com/Wei-Shaw/sub2api/ent/promocodeusage"
"github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
"github.com/Wei-Shaw/sub2api/internal/service"
+
+ entsql "entgo.io/ent/dialect/sql"
)
type promoCodeRepository struct {
@@ -137,11 +140,14 @@ func (r *promoCodeRepository) ListWithFilters(ctx context.Context, params pagina
return nil, nil, err
}
- codes, err := q.
+ codesQuery := q.
Offset(params.Offset()).
- Limit(params.Limit()).
- Order(dbent.Desc(promocode.FieldID)).
- All(ctx)
+ Limit(params.Limit())
+ for _, order := range promoCodeListOrder(params) {
+ codesQuery = codesQuery.Order(order)
+ }
+
+ codes, err := codesQuery.All(ctx)
if err != nil {
return nil, nil, err
}
@@ -151,6 +157,32 @@ func (r *promoCodeRepository) ListWithFilters(ctx context.Context, params pagina
return outCodes, paginationResultFromTotal(int64(total), params), nil
}
+func promoCodeListOrder(params pagination.PaginationParams) []func(*entsql.Selector) {
+ sortBy := strings.ToLower(strings.TrimSpace(params.SortBy))
+ sortOrder := params.NormalizedSortOrder(pagination.SortOrderDesc)
+
+ var field string
+ switch sortBy {
+ case "bonus_amount":
+ field = promocode.FieldBonusAmount
+ case "status":
+ field = promocode.FieldStatus
+ case "expires_at":
+ field = promocode.FieldExpiresAt
+ case "created_at":
+ field = promocode.FieldCreatedAt
+ case "code":
+ field = promocode.FieldCode
+ default:
+ field = promocode.FieldID
+ }
+
+ if sortOrder == pagination.SortOrderAsc {
+ return []func(*entsql.Selector){dbent.Asc(field), dbent.Asc(promocode.FieldID)}
+ }
+ return []func(*entsql.Selector){dbent.Desc(field), dbent.Desc(promocode.FieldID)}
+}
+
func (r *promoCodeRepository) CreateUsage(ctx context.Context, usage *service.PromoCodeUsage) error {
client := clientFromContext(ctx, r.client)
created, err := client.PromoCodeUsage.Create().
diff --git a/backend/internal/repository/proxy_repo.go b/backend/internal/repository/proxy_repo.go
index 07c2a2049844c3f1153be2eacb5e0a44476275ef..60b2f069ecb52c764a63ed81751bb506d788459a 100644
--- a/backend/internal/repository/proxy_repo.go
+++ b/backend/internal/repository/proxy_repo.go
@@ -3,12 +3,16 @@ package repository
import (
"context"
"database/sql"
+ "sort"
+ "strings"
dbent "github.com/Wei-Shaw/sub2api/ent"
"github.com/Wei-Shaw/sub2api/ent/proxy"
"github.com/Wei-Shaw/sub2api/internal/service"
"github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
+
+ entsql "entgo.io/ent/dialect/sql"
)
type sqlQuerier interface {
@@ -135,11 +139,14 @@ func (r *proxyRepository) ListWithFilters(ctx context.Context, params pagination
return nil, nil, err
}
- proxies, err := q.
+ proxiesQuery := q.
Offset(params.Offset()).
- Limit(params.Limit()).
- Order(dbent.Desc(proxy.FieldID)).
- All(ctx)
+ Limit(params.Limit())
+ for _, order := range proxyListOrder(params) {
+ proxiesQuery = proxiesQuery.Order(order)
+ }
+
+ proxies, err := proxiesQuery.All(ctx)
if err != nil {
return nil, nil, err
}
@@ -170,22 +177,58 @@ func (r *proxyRepository) ListWithFiltersAndAccountCount(ctx context.Context, pa
return nil, nil, err
}
- proxies, err := q.
+ if strings.EqualFold(strings.TrimSpace(params.SortBy), "account_count") {
+ return r.listWithAccountCountSort(ctx, q, params, total)
+ }
+
+ proxiesQuery := q.
Offset(params.Offset()).
- Limit(params.Limit()).
+ Limit(params.Limit())
+ for _, order := range proxyListOrder(params) {
+ proxiesQuery = proxiesQuery.Order(order)
+ }
+
+ proxies, err := proxiesQuery.All(ctx)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ return r.buildProxyWithAccountCountResult(ctx, proxies, params, int64(total))
+}
+
+func (r *proxyRepository) listWithAccountCountSort(ctx context.Context, q *dbent.ProxyQuery, params pagination.PaginationParams, total int) ([]service.ProxyWithAccountCount, *pagination.PaginationResult, error) {
+ proxies, err := q.
Order(dbent.Desc(proxy.FieldID)).
All(ctx)
if err != nil {
return nil, nil, err
}
- // Get account counts
+ result, _, err := r.buildProxyWithAccountCountResult(ctx, proxies, params, int64(total))
+ if err != nil {
+ return nil, nil, err
+ }
+
+ sortOrder := params.NormalizedSortOrder(pagination.SortOrderDesc)
+ sort.SliceStable(result, func(i, j int) bool {
+ if result[i].AccountCount == result[j].AccountCount {
+ return result[i].ID > result[j].ID
+ }
+ if sortOrder == pagination.SortOrderAsc {
+ return result[i].AccountCount < result[j].AccountCount
+ }
+ return result[i].AccountCount > result[j].AccountCount
+ })
+
+ return paginateSlice(result, params), paginationResultFromTotal(int64(total), params), nil
+}
+
+func (r *proxyRepository) buildProxyWithAccountCountResult(ctx context.Context, proxies []*dbent.Proxy, params pagination.PaginationParams, total int64) ([]service.ProxyWithAccountCount, *pagination.PaginationResult, error) {
counts, err := r.GetAccountCountsForProxies(ctx)
if err != nil {
return nil, nil, err
}
- // Build result with account counts
result := make([]service.ProxyWithAccountCount, 0, len(proxies))
for i := range proxies {
proxyOut := proxyEntityToService(proxies[i])
@@ -198,7 +241,31 @@ func (r *proxyRepository) ListWithFiltersAndAccountCount(ctx context.Context, pa
})
}
- return result, paginationResultFromTotal(int64(total), params), nil
+ return result, paginationResultFromTotal(total, params), nil
+}
+
+func proxyListOrder(params pagination.PaginationParams) []func(*entsql.Selector) {
+ sortBy := strings.ToLower(strings.TrimSpace(params.SortBy))
+ sortOrder := params.NormalizedSortOrder(pagination.SortOrderDesc)
+
+ var field string
+ switch sortBy {
+ case "name":
+ field = proxy.FieldName
+ case "protocol":
+ field = proxy.FieldProtocol
+ case "status":
+ field = proxy.FieldStatus
+ case "created_at":
+ field = proxy.FieldCreatedAt
+ default:
+ field = proxy.FieldID
+ }
+
+ if sortOrder == pagination.SortOrderAsc {
+ return []func(*entsql.Selector){dbent.Asc(field), dbent.Asc(proxy.FieldID)}
+ }
+ return []func(*entsql.Selector){dbent.Desc(field), dbent.Desc(proxy.FieldID)}
}
func (r *proxyRepository) ListActive(ctx context.Context) ([]service.Proxy, error) {
diff --git a/backend/internal/repository/proxy_repo_sort_integration_test.go b/backend/internal/repository/proxy_repo_sort_integration_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..fe1c2873ba2003c5e465f9665d72825bc14011d9
--- /dev/null
+++ b/backend/internal/repository/proxy_repo_sort_integration_test.go
@@ -0,0 +1,28 @@
+//go:build integration
+
+package repository
+
+import (
+ "github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
+ "github.com/Wei-Shaw/sub2api/internal/service"
+)
+
+func (s *ProxyRepoSuite) TestListWithFiltersAndAccountCount_SortByAccountCountDesc() {
+ p1 := s.mustCreateProxy(&service.Proxy{Name: "p1", Protocol: "http", Host: "127.0.0.1", Port: 8080, Status: service.StatusActive})
+ p2 := s.mustCreateProxy(&service.Proxy{Name: "p2", Protocol: "http", Host: "127.0.0.1", Port: 8081, Status: service.StatusActive})
+ s.mustInsertAccount("a1", &p1.ID)
+ s.mustInsertAccount("a2", &p1.ID)
+ s.mustInsertAccount("a3", &p2.ID)
+
+ proxies, _, err := s.repo.ListWithFiltersAndAccountCount(s.ctx, pagination.PaginationParams{
+ Page: 1,
+ PageSize: 10,
+ SortBy: "account_count",
+ SortOrder: "desc",
+ }, "", "", "")
+ s.Require().NoError(err)
+ s.Require().Len(proxies, 2)
+ s.Require().Equal(p1.ID, proxies[0].ID)
+ s.Require().Equal(int64(2), proxies[0].AccountCount)
+ s.Require().Equal(p2.ID, proxies[1].ID)
+}
diff --git a/backend/internal/repository/redeem_code_repo.go b/backend/internal/repository/redeem_code_repo.go
index 934a30956851aa00a1834d2d91d6b557cbc31ad8..07975970ef86fee547dcd77b26230a6235615a6e 100644
--- a/backend/internal/repository/redeem_code_repo.go
+++ b/backend/internal/repository/redeem_code_repo.go
@@ -2,6 +2,7 @@ package repository
import (
"context"
+ "strings"
"time"
dbent "github.com/Wei-Shaw/sub2api/ent"
@@ -9,6 +10,8 @@ import (
"github.com/Wei-Shaw/sub2api/ent/user"
"github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
"github.com/Wei-Shaw/sub2api/internal/service"
+
+ entsql "entgo.io/ent/dialect/sql"
)
type redeemCodeRepository struct {
@@ -120,13 +123,16 @@ func (r *redeemCodeRepository) ListWithFilters(ctx context.Context, params pagin
return nil, nil, err
}
- codes, err := q.
+ codesQuery := q.
WithUser().
WithGroup().
Offset(params.Offset()).
- Limit(params.Limit()).
- Order(dbent.Desc(redeemcode.FieldID)).
- All(ctx)
+ Limit(params.Limit())
+ for _, order := range redeemCodeListOrder(params) {
+ codesQuery = codesQuery.Order(order)
+ }
+
+ codes, err := codesQuery.All(ctx)
if err != nil {
return nil, nil, err
}
@@ -136,6 +142,34 @@ func (r *redeemCodeRepository) ListWithFilters(ctx context.Context, params pagin
return outCodes, paginationResultFromTotal(int64(total), params), nil
}
+func redeemCodeListOrder(params pagination.PaginationParams) []func(*entsql.Selector) {
+ sortBy := strings.ToLower(strings.TrimSpace(params.SortBy))
+ sortOrder := params.NormalizedSortOrder(pagination.SortOrderDesc)
+
+ var field string
+ switch sortBy {
+ case "type":
+ field = redeemcode.FieldType
+ case "value":
+ field = redeemcode.FieldValue
+ case "status":
+ field = redeemcode.FieldStatus
+ case "used_at":
+ field = redeemcode.FieldUsedAt
+ case "created_at":
+ field = redeemcode.FieldCreatedAt
+ case "code":
+ field = redeemcode.FieldCode
+ default:
+ field = redeemcode.FieldID
+ }
+
+ if sortOrder == pagination.SortOrderAsc {
+ return []func(*entsql.Selector){dbent.Asc(field), dbent.Asc(redeemcode.FieldID)}
+ }
+ return []func(*entsql.Selector){dbent.Desc(field), dbent.Desc(redeemcode.FieldID)}
+}
+
func (r *redeemCodeRepository) Update(ctx context.Context, code *service.RedeemCode) error {
up := r.client.RedeemCode.UpdateOneID(code.ID).
SetCode(code.Code).
diff --git a/backend/internal/repository/redeem_code_repo_sort_integration_test.go b/backend/internal/repository/redeem_code_repo_sort_integration_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..30d32f4cf97a7e551f6a52e3f38670b361a7dfc5
--- /dev/null
+++ b/backend/internal/repository/redeem_code_repo_sort_integration_test.go
@@ -0,0 +1,24 @@
+//go:build integration
+
+package repository
+
+import (
+ "github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
+ "github.com/Wei-Shaw/sub2api/internal/service"
+)
+
+func (s *RedeemCodeRepoSuite) TestListWithFilters_SortByValueAsc() {
+ s.Require().NoError(s.repo.Create(s.ctx, &service.RedeemCode{Code: "VALUE-20", Type: service.RedeemTypeBalance, Value: 20, Status: service.StatusUnused}))
+ s.Require().NoError(s.repo.Create(s.ctx, &service.RedeemCode{Code: "VALUE-10", Type: service.RedeemTypeBalance, Value: 10, Status: service.StatusUnused}))
+
+ codes, _, err := s.repo.ListWithFilters(s.ctx, pagination.PaginationParams{
+ Page: 1,
+ PageSize: 10,
+ SortBy: "value",
+ SortOrder: "asc",
+ }, "", "", "")
+ s.Require().NoError(err)
+ s.Require().Len(codes, 2)
+ s.Require().Equal("VALUE-10", codes[0].Code)
+ s.Require().Equal("VALUE-20", codes[1].Code)
+}
diff --git a/backend/internal/repository/scheduler_cache.go b/backend/internal/repository/scheduler_cache.go
index 4f447e4fea95c187940b802fb7cba936a907b0b0..e9be8c7a6e0eaa99c4019b91e875ef92642dce9c 100644
--- a/backend/internal/repository/scheduler_cache.go
+++ b/backend/internal/repository/scheduler_cache.go
@@ -15,19 +15,39 @@ const (
schedulerBucketSetKey = "sched:buckets"
schedulerOutboxWatermarkKey = "sched:outbox:watermark"
schedulerAccountPrefix = "sched:acc:"
+ schedulerAccountMetaPrefix = "sched:meta:"
schedulerActivePrefix = "sched:active:"
schedulerReadyPrefix = "sched:ready:"
schedulerVersionPrefix = "sched:ver:"
schedulerSnapshotPrefix = "sched:"
schedulerLockPrefix = "sched:lock:"
+
+ defaultSchedulerSnapshotMGetChunkSize = 128
+ defaultSchedulerSnapshotWriteChunkSize = 256
)
type schedulerCache struct {
- rdb *redis.Client
+ rdb *redis.Client
+ mgetChunkSize int
+ writeChunkSize int
}
func NewSchedulerCache(rdb *redis.Client) service.SchedulerCache {
- return &schedulerCache{rdb: rdb}
+ return newSchedulerCacheWithChunkSizes(rdb, defaultSchedulerSnapshotMGetChunkSize, defaultSchedulerSnapshotWriteChunkSize)
+}
+
+func newSchedulerCacheWithChunkSizes(rdb *redis.Client, mgetChunkSize, writeChunkSize int) service.SchedulerCache {
+ if mgetChunkSize <= 0 {
+ mgetChunkSize = defaultSchedulerSnapshotMGetChunkSize
+ }
+ if writeChunkSize <= 0 {
+ writeChunkSize = defaultSchedulerSnapshotWriteChunkSize
+ }
+ return &schedulerCache{
+ rdb: rdb,
+ mgetChunkSize: mgetChunkSize,
+ writeChunkSize: writeChunkSize,
+ }
}
func (c *schedulerCache) GetSnapshot(ctx context.Context, bucket service.SchedulerBucket) ([]*service.Account, bool, error) {
@@ -65,9 +85,9 @@ func (c *schedulerCache) GetSnapshot(ctx context.Context, bucket service.Schedul
keys := make([]string, 0, len(ids))
for _, id := range ids {
- keys = append(keys, schedulerAccountKey(id))
+ keys = append(keys, schedulerAccountMetaKey(id))
}
- values, err := c.rdb.MGet(ctx, keys...).Result()
+ values, err := c.mgetChunked(ctx, keys)
if err != nil {
return nil, false, err
}
@@ -100,14 +120,11 @@ func (c *schedulerCache) SetSnapshot(ctx context.Context, bucket service.Schedul
versionStr := strconv.FormatInt(version, 10)
snapshotKey := schedulerSnapshotKey(bucket, versionStr)
- pipe := c.rdb.Pipeline()
- for _, account := range accounts {
- payload, err := json.Marshal(account)
- if err != nil {
- return err
- }
- pipe.Set(ctx, schedulerAccountKey(strconv.FormatInt(account.ID, 10)), payload, 0)
+ if err := c.writeAccounts(ctx, accounts); err != nil {
+ return err
}
+
+ pipe := c.rdb.Pipeline()
if len(accounts) > 0 {
// 使用序号作为 score,保持数据库返回的排序语义。
members := make([]redis.Z, 0, len(accounts))
@@ -117,7 +134,13 @@ func (c *schedulerCache) SetSnapshot(ctx context.Context, bucket service.Schedul
Member: strconv.FormatInt(account.ID, 10),
})
}
- pipe.ZAdd(ctx, snapshotKey, members...)
+ for start := 0; start < len(members); start += c.writeChunkSize {
+ end := start + c.writeChunkSize
+ if end > len(members) {
+ end = len(members)
+ }
+ pipe.ZAdd(ctx, snapshotKey, members[start:end]...)
+ }
} else {
pipe.Del(ctx, snapshotKey)
}
@@ -151,20 +174,15 @@ func (c *schedulerCache) SetAccount(ctx context.Context, account *service.Accoun
if account == nil || account.ID <= 0 {
return nil
}
- payload, err := json.Marshal(account)
- if err != nil {
- return err
- }
- key := schedulerAccountKey(strconv.FormatInt(account.ID, 10))
- return c.rdb.Set(ctx, key, payload, 0).Err()
+ return c.writeAccounts(ctx, []service.Account{*account})
}
func (c *schedulerCache) DeleteAccount(ctx context.Context, accountID int64) error {
if accountID <= 0 {
return nil
}
- key := schedulerAccountKey(strconv.FormatInt(accountID, 10))
- return c.rdb.Del(ctx, key).Err()
+ id := strconv.FormatInt(accountID, 10)
+ return c.rdb.Del(ctx, schedulerAccountKey(id), schedulerAccountMetaKey(id)).Err()
}
func (c *schedulerCache) UpdateLastUsed(ctx context.Context, updates map[int64]time.Time) error {
@@ -179,7 +197,7 @@ func (c *schedulerCache) UpdateLastUsed(ctx context.Context, updates map[int64]t
ids = append(ids, id)
}
- values, err := c.rdb.MGet(ctx, keys...).Result()
+ values, err := c.mgetChunked(ctx, keys)
if err != nil {
return err
}
@@ -198,7 +216,12 @@ func (c *schedulerCache) UpdateLastUsed(ctx context.Context, updates map[int64]t
if err != nil {
return err
}
+ metaPayload, err := json.Marshal(buildSchedulerMetadataAccount(*account))
+ if err != nil {
+ return err
+ }
pipe.Set(ctx, keys[i], updated, 0)
+ pipe.Set(ctx, schedulerAccountMetaKey(strconv.FormatInt(ids[i], 10)), metaPayload, 0)
}
_, err = pipe.Exec(ctx)
return err
@@ -256,6 +279,10 @@ func schedulerAccountKey(id string) string {
return schedulerAccountPrefix + id
}
+func schedulerAccountMetaKey(id string) string {
+ return schedulerAccountMetaPrefix + id
+}
+
func ptrTime(t time.Time) *time.Time {
return &t
}
@@ -276,3 +303,138 @@ func decodeCachedAccount(val any) (*service.Account, error) {
}
return &account, nil
}
+
+func (c *schedulerCache) writeAccounts(ctx context.Context, accounts []service.Account) error {
+ if len(accounts) == 0 {
+ return nil
+ }
+
+ pipe := c.rdb.Pipeline()
+ pending := 0
+ flush := func() error {
+ if pending == 0 {
+ return nil
+ }
+ if _, err := pipe.Exec(ctx); err != nil {
+ return err
+ }
+ pipe = c.rdb.Pipeline()
+ pending = 0
+ return nil
+ }
+
+ for _, account := range accounts {
+ fullPayload, err := json.Marshal(account)
+ if err != nil {
+ return err
+ }
+ metaPayload, err := json.Marshal(buildSchedulerMetadataAccount(account))
+ if err != nil {
+ return err
+ }
+
+ id := strconv.FormatInt(account.ID, 10)
+ pipe.Set(ctx, schedulerAccountKey(id), fullPayload, 0)
+ pipe.Set(ctx, schedulerAccountMetaKey(id), metaPayload, 0)
+ pending++
+ if pending >= c.writeChunkSize {
+ if err := flush(); err != nil {
+ return err
+ }
+ }
+ }
+
+ return flush()
+}
+
+func (c *schedulerCache) mgetChunked(ctx context.Context, keys []string) ([]any, error) {
+ if len(keys) == 0 {
+ return []any{}, nil
+ }
+
+ out := make([]any, 0, len(keys))
+ chunkSize := c.mgetChunkSize
+ if chunkSize <= 0 {
+ chunkSize = defaultSchedulerSnapshotMGetChunkSize
+ }
+ for start := 0; start < len(keys); start += chunkSize {
+ end := start + chunkSize
+ if end > len(keys) {
+ end = len(keys)
+ }
+ part, err := c.rdb.MGet(ctx, keys[start:end]...).Result()
+ if err != nil {
+ return nil, err
+ }
+ out = append(out, part...)
+ }
+ return out, nil
+}
+
+func buildSchedulerMetadataAccount(account service.Account) service.Account {
+ return service.Account{
+ ID: account.ID,
+ Name: account.Name,
+ Platform: account.Platform,
+ Type: account.Type,
+ Concurrency: account.Concurrency,
+ LoadFactor: account.LoadFactor,
+ Priority: account.Priority,
+ RateMultiplier: account.RateMultiplier,
+ Status: account.Status,
+ LastUsedAt: account.LastUsedAt,
+ ExpiresAt: account.ExpiresAt,
+ AutoPauseOnExpired: account.AutoPauseOnExpired,
+ Schedulable: account.Schedulable,
+ RateLimitedAt: account.RateLimitedAt,
+ RateLimitResetAt: account.RateLimitResetAt,
+ OverloadUntil: account.OverloadUntil,
+ TempUnschedulableUntil: account.TempUnschedulableUntil,
+ TempUnschedulableReason: account.TempUnschedulableReason,
+ SessionWindowStart: account.SessionWindowStart,
+ SessionWindowEnd: account.SessionWindowEnd,
+ SessionWindowStatus: account.SessionWindowStatus,
+ Credentials: filterSchedulerCredentials(account.Credentials),
+ Extra: filterSchedulerExtra(account.Extra),
+ }
+}
+
+func filterSchedulerCredentials(credentials map[string]any) map[string]any {
+ if len(credentials) == 0 {
+ return nil
+ }
+ keys := []string{"model_mapping", "api_key", "project_id", "oauth_type"}
+ filtered := make(map[string]any)
+ for _, key := range keys {
+ if value, ok := credentials[key]; ok && value != nil {
+ filtered[key] = value
+ }
+ }
+ if len(filtered) == 0 {
+ return nil
+ }
+ return filtered
+}
+
+func filterSchedulerExtra(extra map[string]any) map[string]any {
+ if len(extra) == 0 {
+ return nil
+ }
+ keys := []string{
+ "mixed_scheduling",
+ "window_cost_limit",
+ "window_cost_sticky_reserve",
+ "max_sessions",
+ "session_idle_timeout_minutes",
+ }
+ filtered := make(map[string]any)
+ for _, key := range keys {
+ if value, ok := extra[key]; ok && value != nil {
+ filtered[key] = value
+ }
+ }
+ if len(filtered) == 0 {
+ return nil
+ }
+ return filtered
+}
diff --git a/backend/internal/repository/scheduler_cache_integration_test.go b/backend/internal/repository/scheduler_cache_integration_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..134a6a0753f04f3a1fa0bc0f184822aa47609578
--- /dev/null
+++ b/backend/internal/repository/scheduler_cache_integration_test.go
@@ -0,0 +1,88 @@
+//go:build integration
+
+package repository
+
+import (
+ "context"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/Wei-Shaw/sub2api/internal/service"
+ "github.com/stretchr/testify/require"
+)
+
+func TestSchedulerCacheSnapshotUsesSlimMetadataButKeepsFullAccount(t *testing.T) {
+ ctx := context.Background()
+ rdb := testRedis(t)
+ cache := NewSchedulerCache(rdb)
+
+ bucket := service.SchedulerBucket{GroupID: 2, Platform: service.PlatformGemini, Mode: service.SchedulerModeSingle}
+ now := time.Now().UTC().Truncate(time.Second)
+ limitReset := now.Add(10 * time.Minute)
+ overloadUntil := now.Add(2 * time.Minute)
+ tempUnschedUntil := now.Add(3 * time.Minute)
+ windowEnd := now.Add(5 * time.Hour)
+
+ account := service.Account{
+ ID: 101,
+ Name: "gemini-heavy",
+ Platform: service.PlatformGemini,
+ Type: service.AccountTypeOAuth,
+ Status: service.StatusActive,
+ Schedulable: true,
+ Concurrency: 3,
+ Priority: 7,
+ LastUsedAt: &now,
+ Credentials: map[string]any{
+ "api_key": "gemini-api-key",
+ "access_token": "secret-access-token",
+ "project_id": "proj-1",
+ "oauth_type": "ai_studio",
+ "model_mapping": map[string]any{"gemini-2.5-pro": "gemini-2.5-pro"},
+ "huge_blob": strings.Repeat("x", 4096),
+ },
+ Extra: map[string]any{
+ "mixed_scheduling": true,
+ "window_cost_limit": 12.5,
+ "window_cost_sticky_reserve": 8.0,
+ "max_sessions": 4,
+ "session_idle_timeout_minutes": 11,
+ "unused_large_field": strings.Repeat("y", 4096),
+ },
+ RateLimitResetAt: &limitReset,
+ OverloadUntil: &overloadUntil,
+ TempUnschedulableUntil: &tempUnschedUntil,
+ SessionWindowStart: &now,
+ SessionWindowEnd: &windowEnd,
+ SessionWindowStatus: "active",
+ }
+
+ require.NoError(t, cache.SetSnapshot(ctx, bucket, []service.Account{account}))
+
+ snapshot, hit, err := cache.GetSnapshot(ctx, bucket)
+ require.NoError(t, err)
+ require.True(t, hit)
+ require.Len(t, snapshot, 1)
+
+ got := snapshot[0]
+ require.NotNil(t, got)
+ require.Equal(t, "gemini-api-key", got.GetCredential("api_key"))
+ require.Equal(t, "proj-1", got.GetCredential("project_id"))
+ require.Equal(t, "ai_studio", got.GetCredential("oauth_type"))
+ require.NotEmpty(t, got.GetModelMapping())
+ require.Empty(t, got.GetCredential("access_token"))
+ require.Empty(t, got.GetCredential("huge_blob"))
+ require.Equal(t, true, got.Extra["mixed_scheduling"])
+ require.Equal(t, 12.5, got.GetWindowCostLimit())
+ require.Equal(t, 8.0, got.GetWindowCostStickyReserve())
+ require.Equal(t, 4, got.GetMaxSessions())
+ require.Equal(t, 11, got.GetSessionIdleTimeoutMinutes())
+ require.Nil(t, got.Extra["unused_large_field"])
+
+ full, err := cache.GetAccount(ctx, account.ID)
+ require.NoError(t, err)
+ require.NotNil(t, full)
+ require.Equal(t, "secret-access-token", full.GetCredential("access_token"))
+ require.Equal(t, strings.Repeat("x", 4096), full.GetCredential("huge_blob"))
+}
diff --git a/backend/internal/repository/usage_log_repo.go b/backend/internal/repository/usage_log_repo.go
index d7bcd0944d06673321412e024186909859486723..3ba2191e50298e7835874b2a09c7c045aa872839 100644
--- a/backend/internal/repository/usage_log_repo.go
+++ b/backend/internal/repository/usage_log_repo.go
@@ -3771,7 +3771,7 @@ func (r *usageLogRepository) listUsageLogsWithPagination(ctx context.Context, wh
limitPos := len(args) + 1
offsetPos := len(args) + 2
listArgs := append(append([]any{}, args...), params.Limit(), params.Offset())
- query := fmt.Sprintf("SELECT %s FROM usage_logs %s ORDER BY id DESC LIMIT $%d OFFSET $%d", usageLogSelectColumns, whereClause, limitPos, offsetPos)
+ query := fmt.Sprintf("SELECT %s FROM usage_logs %s ORDER BY %s LIMIT $%d OFFSET $%d", usageLogSelectColumns, whereClause, usageLogOrderBy(params), limitPos, offsetPos)
logs, err := r.queryUsageLogs(ctx, query, listArgs...)
if err != nil {
return nil, nil, err
@@ -3786,7 +3786,7 @@ func (r *usageLogRepository) listUsageLogsWithFastPagination(ctx context.Context
limitPos := len(args) + 1
offsetPos := len(args) + 2
listArgs := append(append([]any{}, args...), limit+1, offset)
- query := fmt.Sprintf("SELECT %s FROM usage_logs %s ORDER BY id DESC LIMIT $%d OFFSET $%d", usageLogSelectColumns, whereClause, limitPos, offsetPos)
+ query := fmt.Sprintf("SELECT %s FROM usage_logs %s ORDER BY %s LIMIT $%d OFFSET $%d", usageLogSelectColumns, whereClause, usageLogOrderBy(params), limitPos, offsetPos)
logs, err := r.queryUsageLogs(ctx, query, listArgs...)
if err != nil {
@@ -3808,6 +3808,26 @@ func (r *usageLogRepository) listUsageLogsWithFastPagination(ctx context.Context
return logs, paginationResultFromTotal(total, params), nil
}
+func usageLogOrderBy(params pagination.PaginationParams) string {
+ sortBy := strings.ToLower(strings.TrimSpace(params.SortBy))
+ sortOrder := strings.ToUpper(params.NormalizedSortOrder(pagination.SortOrderDesc))
+
+ var column string
+ switch sortBy {
+ case "model":
+ column = "COALESCE(NULLIF(TRIM(requested_model), ''), model)"
+ case "created_at":
+ column = "created_at"
+ default:
+ column = "id"
+ }
+
+ if column == "id" {
+ return fmt.Sprintf("id %s", sortOrder)
+ }
+ return fmt.Sprintf("%s %s, id %s", column, sortOrder, sortOrder)
+}
+
func (r *usageLogRepository) queryUsageLogs(ctx context.Context, query string, args ...any) (logs []service.UsageLog, err error) {
rows, err := r.sql.QueryContext(ctx, query, args...)
if err != nil {
diff --git a/backend/internal/repository/usage_log_repo_request_type_test.go b/backend/internal/repository/usage_log_repo_request_type_test.go
index ce0c5f004029cc84d33d7285dc2cd7fc65713c72..b9cb6a13302a8eafd6ff3ee30728fb7d505d5bda 100644
--- a/backend/internal/repository/usage_log_repo_request_type_test.go
+++ b/backend/internal/repository/usage_log_repo_request_type_test.go
@@ -330,6 +330,15 @@ func TestUsageLogRepositoryGetStatsWithFiltersRequestTypePriority(t *testing.T)
"total_account_cost",
"avg_duration_ms",
}).AddRow(int64(1), int64(2), int64(3), int64(4), 1.2, 1.0, 1.2, 20.0))
+ mock.ExpectQuery("SELECT COALESCE\\(NULLIF\\(TRIM\\(inbound_endpoint\\), ''\\), 'unknown'\\) AS endpoint").
+ WithArgs(sqlmock.AnyArg(), sqlmock.AnyArg(), requestType).
+ WillReturnRows(sqlmock.NewRows([]string{"endpoint", "requests", "total_tokens", "cost", "actual_cost"}))
+ mock.ExpectQuery("SELECT COALESCE\\(NULLIF\\(TRIM\\(upstream_endpoint\\), ''\\), 'unknown'\\) AS endpoint").
+ WithArgs(sqlmock.AnyArg(), sqlmock.AnyArg(), requestType).
+ WillReturnRows(sqlmock.NewRows([]string{"endpoint", "requests", "total_tokens", "cost", "actual_cost"}))
+ mock.ExpectQuery("SELECT CONCAT\\(").
+ WithArgs(sqlmock.AnyArg(), sqlmock.AnyArg(), requestType).
+ WillReturnRows(sqlmock.NewRows([]string{"endpoint", "requests", "total_tokens", "cost", "actual_cost"}))
stats, err := repo.GetStatsWithFilters(context.Background(), filters)
require.NoError(t, err)
diff --git a/backend/internal/repository/usage_log_repo_sort_integration_test.go b/backend/internal/repository/usage_log_repo_sort_integration_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..4c69f97538ef2dfac45b1a06617544e943c76621
--- /dev/null
+++ b/backend/internal/repository/usage_log_repo_sort_integration_test.go
@@ -0,0 +1,61 @@
+//go:build integration
+
+package repository
+
+import (
+ "time"
+
+ "github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
+ "github.com/Wei-Shaw/sub2api/internal/pkg/usagestats"
+ "github.com/Wei-Shaw/sub2api/internal/service"
+ "github.com/google/uuid"
+)
+
+func (s *UsageLogRepoSuite) TestListWithFilters_SortByModelAsc() {
+ user := mustCreateUser(s.T(), s.client, &service.User{Email: "usage-sort@example.com"})
+ apiKey := mustCreateApiKey(s.T(), s.client, &service.APIKey{UserID: user.ID, Key: "sk-usage-sort", Name: "k"})
+ account := mustCreateAccount(s.T(), s.client, &service.Account{Name: "usage-sort-account"})
+
+ first := &service.UsageLog{
+ UserID: user.ID,
+ APIKeyID: apiKey.ID,
+ AccountID: account.ID,
+ RequestID: uuid.New().String(),
+ Model: "z-model",
+ RequestedModel: "z-model",
+ InputTokens: 10,
+ OutputTokens: 20,
+ TotalCost: 0.5,
+ ActualCost: 0.5,
+ CreatedAt: time.Now(),
+ }
+ _, err := s.repo.Create(s.ctx, first)
+ s.Require().NoError(err)
+
+ second := &service.UsageLog{
+ UserID: user.ID,
+ APIKeyID: apiKey.ID,
+ AccountID: account.ID,
+ RequestID: uuid.New().String(),
+ Model: "a-model",
+ RequestedModel: "a-model",
+ InputTokens: 10,
+ OutputTokens: 20,
+ TotalCost: 0.5,
+ ActualCost: 0.5,
+ CreatedAt: time.Now().Add(time.Second),
+ }
+ _, err = s.repo.Create(s.ctx, second)
+ s.Require().NoError(err)
+
+ logs, _, err := s.repo.ListWithFilters(s.ctx, pagination.PaginationParams{
+ Page: 1,
+ PageSize: 10,
+ SortBy: "model",
+ SortOrder: "asc",
+ }, usagestats.UsageLogFilters{UserID: user.ID})
+ s.Require().NoError(err)
+ s.Require().Len(logs, 2)
+ s.Require().Equal("a-model", logs[0].RequestedModel)
+ s.Require().Equal("z-model", logs[1].RequestedModel)
+}
diff --git a/backend/internal/repository/user_repo.go b/backend/internal/repository/user_repo.go
index 06c79113e2e2df4956884b8a26b8e0786510b0b8..d5a13607aef5eb743530120eab2d2be77f5e94fc 100644
--- a/backend/internal/repository/user_repo.go
+++ b/backend/internal/repository/user_repo.go
@@ -17,6 +17,8 @@ import (
"github.com/Wei-Shaw/sub2api/ent/usersubscription"
"github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
"github.com/Wei-Shaw/sub2api/internal/service"
+
+ entsql "entgo.io/ent/dialect/sql"
)
type userRepository struct {
@@ -224,11 +226,14 @@ func (r *userRepository) ListWithFilters(ctx context.Context, params pagination.
return nil, nil, err
}
- users, err := q.
+ usersQuery := q.
Offset(params.Offset()).
- Limit(params.Limit()).
- Order(dbent.Desc(dbuser.FieldID)).
- All(ctx)
+ Limit(params.Limit())
+ for _, order := range userListOrder(params) {
+ usersQuery = usersQuery.Order(order)
+ }
+
+ users, err := usersQuery.All(ctx)
if err != nil {
return nil, nil, err
}
@@ -281,6 +286,50 @@ func (r *userRepository) ListWithFilters(ctx context.Context, params pagination.
return outUsers, paginationResultFromTotal(int64(total), params), nil
}
+func userListOrder(params pagination.PaginationParams) []func(*entsql.Selector) {
+ sortBy := strings.ToLower(strings.TrimSpace(params.SortBy))
+ sortOrder := params.NormalizedSortOrder(pagination.SortOrderDesc)
+
+ var field string
+ defaultField := true
+ switch sortBy {
+ case "email":
+ field = dbuser.FieldEmail
+ defaultField = false
+ case "username":
+ field = dbuser.FieldUsername
+ defaultField = false
+ case "role":
+ field = dbuser.FieldRole
+ defaultField = false
+ case "balance":
+ field = dbuser.FieldBalance
+ defaultField = false
+ case "concurrency":
+ field = dbuser.FieldConcurrency
+ defaultField = false
+ case "status":
+ field = dbuser.FieldStatus
+ defaultField = false
+ case "created_at":
+ field = dbuser.FieldCreatedAt
+ defaultField = false
+ default:
+ field = dbuser.FieldID
+ }
+
+ if sortOrder == pagination.SortOrderAsc {
+ if defaultField && field == dbuser.FieldID {
+ return []func(*entsql.Selector){dbent.Asc(dbuser.FieldID)}
+ }
+ return []func(*entsql.Selector){dbent.Asc(field), dbent.Asc(dbuser.FieldID)}
+ }
+ if defaultField && field == dbuser.FieldID {
+ return []func(*entsql.Selector){dbent.Desc(dbuser.FieldID)}
+ }
+ return []func(*entsql.Selector){dbent.Desc(field), dbent.Desc(dbuser.FieldID)}
+}
+
// filterUsersByAttributes returns user IDs that match ALL the given attribute filters
func (r *userRepository) filterUsersByAttributes(ctx context.Context, attrs map[int64]string) ([]int64, error) {
if len(attrs) == 0 {
diff --git a/backend/internal/repository/user_repo_sort_integration_test.go b/backend/internal/repository/user_repo_sort_integration_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..ab84b0e93bb969bfaf1c863cdb30067cc15a8942
--- /dev/null
+++ b/backend/internal/repository/user_repo_sort_integration_test.go
@@ -0,0 +1,39 @@
+//go:build integration
+
+package repository
+
+import (
+ "testing"
+
+ "github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
+ "github.com/Wei-Shaw/sub2api/internal/service"
+)
+
+func (s *UserRepoSuite) TestListWithFilters_SortByEmailAsc() {
+ s.mustCreateUser(&service.User{Email: "z-last@example.com", Username: "z-user"})
+ s.mustCreateUser(&service.User{Email: "a-first@example.com", Username: "a-user"})
+
+ users, _, err := s.repo.ListWithFilters(s.ctx, pagination.PaginationParams{
+ Page: 1,
+ PageSize: 10,
+ SortBy: "email",
+ SortOrder: "asc",
+ }, service.UserListFilters{})
+ s.Require().NoError(err)
+ s.Require().Len(users, 2)
+ s.Require().Equal("a-first@example.com", users[0].Email)
+ s.Require().Equal("z-last@example.com", users[1].Email)
+}
+
+func (s *UserRepoSuite) TestList_DefaultSortByNewestFirst() {
+ first := s.mustCreateUser(&service.User{Email: "first@example.com"})
+ second := s.mustCreateUser(&service.User{Email: "second@example.com"})
+
+ users, _, err := s.repo.List(s.ctx, pagination.PaginationParams{Page: 1, PageSize: 10})
+ s.Require().NoError(err)
+ s.Require().Len(users, 2)
+ s.Require().Equal(second.ID, users[0].ID)
+ s.Require().Equal(first.ID, users[1].ID)
+}
+
+func TestUserRepoSortSuiteSmoke(_ *testing.T) {}
diff --git a/backend/internal/repository/wire.go b/backend/internal/repository/wire.go
index 657e3ed66cda3d408ccd810b02dc6c125bac28d7..d3adb4a0aa7b52daf523627c72f2b4ebdff35523 100644
--- a/backend/internal/repository/wire.go
+++ b/backend/internal/repository/wire.go
@@ -47,6 +47,21 @@ func ProvideSessionLimitCache(rdb *redis.Client, cfg *config.Config) service.Ses
return NewSessionLimitCache(rdb, defaultIdleTimeoutMinutes)
}
+// ProvideSchedulerCache 创建调度快照缓存,并注入快照分块参数。
+func ProvideSchedulerCache(rdb *redis.Client, cfg *config.Config) service.SchedulerCache {
+ mgetChunkSize := defaultSchedulerSnapshotMGetChunkSize
+ writeChunkSize := defaultSchedulerSnapshotWriteChunkSize
+ if cfg != nil {
+ if cfg.Gateway.Scheduling.SnapshotMGetChunkSize > 0 {
+ mgetChunkSize = cfg.Gateway.Scheduling.SnapshotMGetChunkSize
+ }
+ if cfg.Gateway.Scheduling.SnapshotWriteChunkSize > 0 {
+ writeChunkSize = cfg.Gateway.Scheduling.SnapshotWriteChunkSize
+ }
+ }
+ return newSchedulerCacheWithChunkSizes(rdb, mgetChunkSize, writeChunkSize)
+}
+
// ProviderSet is the Wire provider set for all repositories
var ProviderSet = wire.NewSet(
NewUserRepository,
@@ -92,7 +107,7 @@ var ProviderSet = wire.NewSet(
NewRedeemCache,
NewUpdateCache,
NewGeminiTokenCache,
- NewSchedulerCache,
+ ProvideSchedulerCache,
NewSchedulerOutboxRepository,
NewProxyLatencyCache,
NewTotpCache,
diff --git a/backend/internal/server/api_contract_test.go b/backend/internal/server/api_contract_test.go
index 24f60f2705f7042786a9497e8e02d25dc576510f..1a4892fa387d2c299cd8f893389cffac75dfafa2 100644
--- a/backend/internal/server/api_contract_test.go
+++ b/backend/internal/server/api_contract_test.go
@@ -462,6 +462,28 @@ func TestAPIContracts(t *testing.T) {
service.SettingKeyTurnstileSiteKey: "site-key",
service.SettingKeyTurnstileSecretKey: "secret-key",
+ service.SettingKeyOIDCConnectEnabled: "false",
+ service.SettingKeyOIDCConnectProviderName: "OIDC",
+ service.SettingKeyOIDCConnectClientID: "",
+ service.SettingKeyOIDCConnectIssuerURL: "",
+ service.SettingKeyOIDCConnectDiscoveryURL: "",
+ service.SettingKeyOIDCConnectAuthorizeURL: "",
+ service.SettingKeyOIDCConnectTokenURL: "",
+ service.SettingKeyOIDCConnectUserInfoURL: "",
+ service.SettingKeyOIDCConnectJWKSURL: "",
+ service.SettingKeyOIDCConnectScopes: "openid email profile",
+ service.SettingKeyOIDCConnectRedirectURL: "",
+ service.SettingKeyOIDCConnectFrontendRedirectURL: "/auth/oidc/callback",
+ service.SettingKeyOIDCConnectTokenAuthMethod: "client_secret_post",
+ service.SettingKeyOIDCConnectUsePKCE: "false",
+ service.SettingKeyOIDCConnectValidateIDToken: "true",
+ service.SettingKeyOIDCConnectAllowedSigningAlgs: "RS256,ES256,PS256",
+ service.SettingKeyOIDCConnectClockSkewSeconds: "120",
+ service.SettingKeyOIDCConnectRequireEmailVerified: "false",
+ service.SettingKeyOIDCConnectUserInfoEmailPath: "",
+ service.SettingKeyOIDCConnectUserInfoIDPath: "",
+ service.SettingKeyOIDCConnectUserInfoUsernamePath: "",
+
service.SettingKeySiteName: "Sub2API",
service.SettingKeySiteLogo: "",
service.SettingKeySiteSubtitle: "Subtitle",
@@ -469,8 +491,10 @@ func TestAPIContracts(t *testing.T) {
service.SettingKeyContactInfo: "support",
service.SettingKeyDocURL: "https://docs.example.com",
- service.SettingKeyDefaultConcurrency: "5",
- service.SettingKeyDefaultBalance: "1.25",
+ service.SettingKeyDefaultConcurrency: "5",
+ service.SettingKeyDefaultBalance: "1.25",
+ service.SettingKeyTableDefaultPageSize: "20",
+ service.SettingKeyTablePageSizeOptions: "[10,20,50,100]",
service.SettingKeyOpsMonitoringEnabled: "false",
service.SettingKeyOpsRealtimeMonitoringEnabled: "true",
@@ -503,10 +527,32 @@ func TestAPIContracts(t *testing.T) {
"turnstile_enabled": true,
"turnstile_site_key": "site-key",
"turnstile_secret_key_configured": true,
- "linuxdo_connect_enabled": false,
+ "linuxdo_connect_enabled": false,
"linuxdo_connect_client_id": "",
"linuxdo_connect_client_secret_configured": false,
"linuxdo_connect_redirect_url": "",
+ "oidc_connect_enabled": false,
+ "oidc_connect_provider_name": "OIDC",
+ "oidc_connect_client_id": "",
+ "oidc_connect_client_secret_configured": false,
+ "oidc_connect_issuer_url": "",
+ "oidc_connect_discovery_url": "",
+ "oidc_connect_authorize_url": "",
+ "oidc_connect_token_url": "",
+ "oidc_connect_userinfo_url": "",
+ "oidc_connect_jwks_url": "",
+ "oidc_connect_scopes": "openid email profile",
+ "oidc_connect_redirect_url": "",
+ "oidc_connect_frontend_redirect_url": "/auth/oidc/callback",
+ "oidc_connect_token_auth_method": "client_secret_post",
+ "oidc_connect_use_pkce": false,
+ "oidc_connect_validate_id_token": true,
+ "oidc_connect_allowed_signing_algs": "RS256,ES256,PS256",
+ "oidc_connect_clock_skew_seconds": 120,
+ "oidc_connect_require_email_verified": false,
+ "oidc_connect_userinfo_email_path": "",
+ "oidc_connect_userinfo_id_path": "",
+ "oidc_connect_userinfo_username_path": "",
"ops_monitoring_enabled": false,
"ops_realtime_monitoring_enabled": true,
"ops_query_mode_default": "auto",
@@ -532,6 +578,8 @@ func TestAPIContracts(t *testing.T) {
"hide_ccs_import_button": false,
"purchase_subscription_enabled": false,
"purchase_subscription_url": "",
+ "table_default_page_size": 20,
+ "table_page_size_options": [10, 20, 50, 100],
"min_claude_code_version": "",
"max_claude_code_version": "",
"allow_ungrouped_key_scheduling": false,
@@ -539,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": []
}
@@ -652,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) {
diff --git a/backend/internal/server/router.go b/backend/internal/server/router.go
index d60a142c873f06942e5a623e862a2b321d54f985..a507b6f831e2fe69163eff38f32ba610067506d0 100644
--- a/backend/internal/server/router.go
+++ b/backend/internal/server/router.go
@@ -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)
}
diff --git a/backend/internal/server/routes/auth.go b/backend/internal/server/routes/auth.go
index a6c0ecf568341798f9b356de228bd194cbcca4ea..c143b030fc88da423867e4bf77404474d9039f78 100644
--- a/backend/internal/server/routes/auth.go
+++ b/backend/internal/server/routes/auth.go
@@ -70,6 +70,14 @@ func RegisterAuthRoutes(
}),
h.Auth.CompleteLinuxDoOAuthRegistration,
)
+ auth.GET("/oauth/oidc/start", h.Auth.OIDCOAuthStart)
+ auth.GET("/oauth/oidc/callback", h.Auth.OIDCOAuthCallback)
+ auth.POST("/oauth/oidc/complete-registration",
+ rateLimiter.LimitWithOptions("oauth-oidc-complete", 10, time.Minute, middleware.RateLimitOptions{
+ FailureMode: middleware.RateLimitFailClose,
+ }),
+ h.Auth.CompleteOIDCOAuthRegistration,
+ )
}
// 公开设置(无需认证)
diff --git a/backend/internal/server/routes/payment.go b/backend/internal/server/routes/payment.go
new file mode 100644
index 0000000000000000000000000000000000000000..6bf04679d417dc2c493934ba16ecb619e8312660
--- /dev/null
+++ b/backend/internal/server/routes/payment.go
@@ -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)
+ }
+ }
+}
diff --git a/backend/internal/service/admin_service.go b/backend/internal/service/admin_service.go
index 8032f8717a5597aab7dea14f3b7a0b15522b9eb9..97b42c2458333774c6667633cc4bf1f0db961ff2 100644
--- a/backend/internal/service/admin_service.go
+++ b/backend/internal/service/admin_service.go
@@ -21,13 +21,13 @@ import (
// AdminService interface defines admin management operations
type AdminService interface {
// User management
- ListUsers(ctx context.Context, page, pageSize int, filters UserListFilters) ([]User, int64, error)
+ ListUsers(ctx context.Context, page, pageSize int, filters UserListFilters, sortBy, sortOrder string) ([]User, int64, error)
GetUser(ctx context.Context, id int64) (*User, error)
CreateUser(ctx context.Context, input *CreateUserInput) (*User, error)
UpdateUser(ctx context.Context, id int64, input *UpdateUserInput) (*User, error)
DeleteUser(ctx context.Context, id int64) error
UpdateUserBalance(ctx context.Context, userID int64, balance float64, operation string, notes string) (*User, error)
- GetUserAPIKeys(ctx context.Context, userID int64, page, pageSize int) ([]APIKey, int64, error)
+ GetUserAPIKeys(ctx context.Context, userID int64, page, pageSize int, sortBy, sortOrder string) ([]APIKey, int64, error)
GetUserUsageStats(ctx context.Context, userID int64, period string) (any, error)
// GetUserBalanceHistory returns paginated balance/concurrency change records for a user.
// codeType is optional - pass empty string to return all types.
@@ -35,7 +35,7 @@ type AdminService interface {
GetUserBalanceHistory(ctx context.Context, userID int64, page, pageSize int, codeType string) ([]RedeemCode, int64, float64, error)
// Group management
- ListGroups(ctx context.Context, page, pageSize int, platform, status, search string, isExclusive *bool) ([]Group, int64, error)
+ ListGroups(ctx context.Context, page, pageSize int, platform, status, search string, isExclusive *bool, sortBy, sortOrder string) ([]Group, int64, error)
GetAllGroups(ctx context.Context) ([]Group, error)
GetAllGroupsByPlatform(ctx context.Context, platform string) ([]Group, error)
GetGroup(ctx context.Context, id int64) (*Group, error)
@@ -55,7 +55,7 @@ type AdminService interface {
ReplaceUserGroup(ctx context.Context, userID, oldGroupID, newGroupID int64) (*ReplaceUserGroupResult, error)
// Account management
- ListAccounts(ctx context.Context, page, pageSize int, platform, accountType, status, search string, groupID int64, privacyMode string) ([]Account, int64, error)
+ ListAccounts(ctx context.Context, page, pageSize int, platform, accountType, status, search string, groupID int64, privacyMode string, sortBy, sortOrder string) ([]Account, int64, error)
GetAccount(ctx context.Context, id int64) (*Account, error)
GetAccountsByIDs(ctx context.Context, ids []int64) ([]*Account, error)
CreateAccount(ctx context.Context, input *CreateAccountInput) (*Account, error)
@@ -77,8 +77,8 @@ type AdminService interface {
CheckMixedChannelRisk(ctx context.Context, currentAccountID int64, currentAccountPlatform string, groupIDs []int64) error
// Proxy management
- ListProxies(ctx context.Context, page, pageSize int, protocol, status, search string) ([]Proxy, int64, error)
- ListProxiesWithAccountCount(ctx context.Context, page, pageSize int, protocol, status, search string) ([]ProxyWithAccountCount, int64, error)
+ ListProxies(ctx context.Context, page, pageSize int, protocol, status, search string, sortBy, sortOrder string) ([]Proxy, int64, error)
+ ListProxiesWithAccountCount(ctx context.Context, page, pageSize int, protocol, status, search string, sortBy, sortOrder string) ([]ProxyWithAccountCount, int64, error)
GetAllProxies(ctx context.Context) ([]Proxy, error)
GetAllProxiesWithAccountCount(ctx context.Context) ([]ProxyWithAccountCount, error)
GetProxy(ctx context.Context, id int64) (*Proxy, error)
@@ -93,7 +93,7 @@ type AdminService interface {
CheckProxyQuality(ctx context.Context, id int64) (*ProxyQualityCheckResult, error)
// Redeem code management
- ListRedeemCodes(ctx context.Context, page, pageSize int, codeType, status, search string) ([]RedeemCode, int64, error)
+ ListRedeemCodes(ctx context.Context, page, pageSize int, codeType, status, search string, sortBy, sortOrder string) ([]RedeemCode, int64, error)
GetRedeemCode(ctx context.Context, id int64) (*RedeemCode, error)
GenerateRedeemCodes(ctx context.Context, input *GenerateRedeemCodesInput) ([]RedeemCode, error)
DeleteRedeemCode(ctx context.Context, id int64) error
@@ -152,10 +152,11 @@ type CreateGroupInput struct {
// 支持的模型系列(仅 antigravity 平台使用)
SupportedModelScopes []string
// OpenAI Messages 调度配置(仅 openai 平台使用)
- AllowMessagesDispatch bool
- DefaultMappedModel string
- RequireOAuthOnly bool
- RequirePrivacySet bool
+ AllowMessagesDispatch bool
+ DefaultMappedModel string
+ RequireOAuthOnly bool
+ RequirePrivacySet bool
+ MessagesDispatchModelConfig OpenAIMessagesDispatchModelConfig
// 从指定分组复制账号(创建分组后在同一事务内绑定)
CopyAccountsFromGroupIDs []int64
}
@@ -186,10 +187,11 @@ type UpdateGroupInput struct {
// 支持的模型系列(仅 antigravity 平台使用)
SupportedModelScopes *[]string
// OpenAI Messages 调度配置(仅 openai 平台使用)
- AllowMessagesDispatch *bool
- DefaultMappedModel *string
- RequireOAuthOnly *bool
- RequirePrivacySet *bool
+ AllowMessagesDispatch *bool
+ DefaultMappedModel *string
+ RequireOAuthOnly *bool
+ RequirePrivacySet *bool
+ MessagesDispatchModelConfig *OpenAIMessagesDispatchModelConfig
// 从指定分组复制账号(同步操作:先清空当前分组的账号绑定,再绑定源分组的账号)
CopyAccountsFromGroupIDs []int64
}
@@ -483,8 +485,8 @@ func NewAdminService(
}
// User management implementations
-func (s *adminServiceImpl) ListUsers(ctx context.Context, page, pageSize int, filters UserListFilters) ([]User, int64, error) {
- params := pagination.PaginationParams{Page: page, PageSize: pageSize}
+func (s *adminServiceImpl) ListUsers(ctx context.Context, page, pageSize int, filters UserListFilters, sortBy, sortOrder string) ([]User, int64, error) {
+ params := pagination.PaginationParams{Page: page, PageSize: pageSize, SortBy: sortBy, SortOrder: sortOrder}
users, result, err := s.userRepo.ListWithFilters(ctx, params, filters)
if err != nil {
return nil, 0, err
@@ -751,8 +753,8 @@ func (s *adminServiceImpl) UpdateUserBalance(ctx context.Context, userID int64,
return user, nil
}
-func (s *adminServiceImpl) GetUserAPIKeys(ctx context.Context, userID int64, page, pageSize int) ([]APIKey, int64, error) {
- params := pagination.PaginationParams{Page: page, PageSize: pageSize}
+func (s *adminServiceImpl) GetUserAPIKeys(ctx context.Context, userID int64, page, pageSize int, sortBy, sortOrder string) ([]APIKey, int64, error) {
+ params := pagination.PaginationParams{Page: page, PageSize: pageSize, SortBy: sortBy, SortOrder: sortOrder}
keys, result, err := s.apiKeyRepo.ListByUserID(ctx, userID, params, APIKeyListFilters{})
if err != nil {
return nil, 0, err
@@ -787,8 +789,8 @@ func (s *adminServiceImpl) GetUserBalanceHistory(ctx context.Context, userID int
}
// Group management implementations
-func (s *adminServiceImpl) ListGroups(ctx context.Context, page, pageSize int, platform, status, search string, isExclusive *bool) ([]Group, int64, error) {
- params := pagination.PaginationParams{Page: page, PageSize: pageSize}
+func (s *adminServiceImpl) ListGroups(ctx context.Context, page, pageSize int, platform, status, search string, isExclusive *bool, sortBy, sortOrder string) ([]Group, int64, error) {
+ params := pagination.PaginationParams{Page: page, PageSize: pageSize, SortBy: sortBy, SortOrder: sortOrder}
groups, result, err := s.groupRepo.ListWithFilters(ctx, params, platform, status, search, isExclusive)
if err != nil {
return nil, 0, err
@@ -908,7 +910,9 @@ func (s *adminServiceImpl) CreateGroup(ctx context.Context, input *CreateGroupIn
RequireOAuthOnly: input.RequireOAuthOnly,
RequirePrivacySet: input.RequirePrivacySet,
DefaultMappedModel: input.DefaultMappedModel,
+ MessagesDispatchModelConfig: normalizeOpenAIMessagesDispatchModelConfig(input.MessagesDispatchModelConfig),
}
+ sanitizeGroupMessagesDispatchFields(group)
if err := s.groupRepo.Create(ctx, group); err != nil {
return nil, err
}
@@ -1135,6 +1139,10 @@ func (s *adminServiceImpl) UpdateGroup(ctx context.Context, id int64, input *Upd
if input.DefaultMappedModel != nil {
group.DefaultMappedModel = *input.DefaultMappedModel
}
+ if input.MessagesDispatchModelConfig != nil {
+ group.MessagesDispatchModelConfig = normalizeOpenAIMessagesDispatchModelConfig(*input.MessagesDispatchModelConfig)
+ }
+ sanitizeGroupMessagesDispatchFields(group)
if err := s.groupRepo.Update(ctx, group); err != nil {
return nil, err
@@ -1456,8 +1464,8 @@ func (s *adminServiceImpl) ReplaceUserGroup(ctx context.Context, userID, oldGrou
}
// Account management implementations
-func (s *adminServiceImpl) ListAccounts(ctx context.Context, page, pageSize int, platform, accountType, status, search string, groupID int64, privacyMode string) ([]Account, int64, error) {
- params := pagination.PaginationParams{Page: page, PageSize: pageSize}
+func (s *adminServiceImpl) ListAccounts(ctx context.Context, page, pageSize int, platform, accountType, status, search string, groupID int64, privacyMode string, sortBy, sortOrder string) ([]Account, int64, error) {
+ params := pagination.PaginationParams{Page: page, PageSize: pageSize, SortBy: sortBy, SortOrder: sortOrder}
accounts, result, err := s.accountRepo.ListWithFilters(ctx, params, platform, accountType, status, search, groupID, privacyMode)
if err != nil {
return nil, 0, err
@@ -1885,8 +1893,8 @@ func (s *adminServiceImpl) SetAccountSchedulable(ctx context.Context, id int64,
}
// Proxy management implementations
-func (s *adminServiceImpl) ListProxies(ctx context.Context, page, pageSize int, protocol, status, search string) ([]Proxy, int64, error) {
- params := pagination.PaginationParams{Page: page, PageSize: pageSize}
+func (s *adminServiceImpl) ListProxies(ctx context.Context, page, pageSize int, protocol, status, search string, sortBy, sortOrder string) ([]Proxy, int64, error) {
+ params := pagination.PaginationParams{Page: page, PageSize: pageSize, SortBy: sortBy, SortOrder: sortOrder}
proxies, result, err := s.proxyRepo.ListWithFilters(ctx, params, protocol, status, search)
if err != nil {
return nil, 0, err
@@ -1894,8 +1902,8 @@ func (s *adminServiceImpl) ListProxies(ctx context.Context, page, pageSize int,
return proxies, result.Total, nil
}
-func (s *adminServiceImpl) ListProxiesWithAccountCount(ctx context.Context, page, pageSize int, protocol, status, search string) ([]ProxyWithAccountCount, int64, error) {
- params := pagination.PaginationParams{Page: page, PageSize: pageSize}
+func (s *adminServiceImpl) ListProxiesWithAccountCount(ctx context.Context, page, pageSize int, protocol, status, search string, sortBy, sortOrder string) ([]ProxyWithAccountCount, int64, error) {
+ params := pagination.PaginationParams{Page: page, PageSize: pageSize, SortBy: sortBy, SortOrder: sortOrder}
proxies, result, err := s.proxyRepo.ListWithFiltersAndAccountCount(ctx, params, protocol, status, search)
if err != nil {
return nil, 0, err
@@ -2032,8 +2040,8 @@ func (s *adminServiceImpl) CheckProxyExists(ctx context.Context, host string, po
}
// Redeem code management implementations
-func (s *adminServiceImpl) ListRedeemCodes(ctx context.Context, page, pageSize int, codeType, status, search string) ([]RedeemCode, int64, error) {
- params := pagination.PaginationParams{Page: page, PageSize: pageSize}
+func (s *adminServiceImpl) ListRedeemCodes(ctx context.Context, page, pageSize int, codeType, status, search string, sortBy, sortOrder string) ([]RedeemCode, int64, error) {
+ params := pagination.PaginationParams{Page: page, PageSize: pageSize, SortBy: sortBy, SortOrder: sortOrder}
codes, result, err := s.redeemCodeRepo.ListWithFilters(ctx, params, codeType, status, search)
if err != nil {
return nil, 0, err
diff --git a/backend/internal/service/admin_service_group_test.go b/backend/internal/service/admin_service_group_test.go
index 536be0b5834ab12a72e7a49a16c23b452ff14107..a4c6d0caba6ef7dc4b4594d20adaf4c13b315a1b 100644
--- a/backend/internal/service/admin_service_group_test.go
+++ b/backend/internal/service/admin_service_group_test.go
@@ -10,6 +10,11 @@ import (
"github.com/stretchr/testify/require"
)
+func ptrString[T ~string](v T) *string {
+ s := string(v)
+ return &s
+}
+
// groupRepoStubForAdmin 用于测试 AdminService 的 GroupRepository Stub
type groupRepoStubForAdmin struct {
created *Group // 记录 Create 调用的参数
@@ -120,6 +125,22 @@ func (s *groupRepoStubForAdmin) UpdateSortOrders(_ context.Context, _ []GroupSor
return nil
}
+func TestAdminService_ListGroups_PassesSortParams(t *testing.T) {
+ repo := &groupRepoStubForAdmin{
+ listWithFiltersGroups: []Group{{ID: 1, Name: "g1"}},
+ }
+ svc := &adminServiceImpl{groupRepo: repo}
+
+ _, _, err := svc.ListGroups(context.Background(), 3, 25, PlatformOpenAI, StatusActive, "needle", nil, "account_count", "ASC")
+ require.NoError(t, err)
+ require.Equal(t, pagination.PaginationParams{
+ Page: 3,
+ PageSize: 25,
+ SortBy: "account_count",
+ SortOrder: "ASC",
+ }, repo.listWithFiltersParams)
+}
+
// TestAdminService_CreateGroup_WithImagePricing 测试创建分组时 ImagePrice 字段正确传递
func TestAdminService_CreateGroup_WithImagePricing(t *testing.T) {
repo := &groupRepoStubForAdmin{}
@@ -245,6 +266,116 @@ func TestAdminService_UpdateGroup_PartialImagePricing(t *testing.T) {
require.Nil(t, repo.updated.ImagePrice4K)
}
+func TestAdminService_CreateGroup_NormalizesMessagesDispatchModelConfig(t *testing.T) {
+ repo := &groupRepoStubForAdmin{}
+ svc := &adminServiceImpl{groupRepo: repo}
+
+ group, err := svc.CreateGroup(context.Background(), &CreateGroupInput{
+ Name: "dispatch-group",
+ Description: "dispatch config",
+ Platform: PlatformOpenAI,
+ RateMultiplier: 1.0,
+ MessagesDispatchModelConfig: OpenAIMessagesDispatchModelConfig{
+ OpusMappedModel: " gpt-5.4-high ",
+ SonnetMappedModel: " gpt-5.3-codex ",
+ HaikuMappedModel: " gpt-5.4-mini-medium ",
+ ExactModelMappings: map[string]string{
+ " claude-sonnet-4-5-20250929 ": " gpt-5.2-high ",
+ },
+ },
+ })
+ require.NoError(t, err)
+ require.NotNil(t, group)
+ require.NotNil(t, repo.created)
+ require.Equal(t, OpenAIMessagesDispatchModelConfig{
+ OpusMappedModel: "gpt-5.4",
+ SonnetMappedModel: "gpt-5.3-codex",
+ HaikuMappedModel: "gpt-5.4-mini",
+ ExactModelMappings: map[string]string{
+ "claude-sonnet-4-5-20250929": "gpt-5.2",
+ },
+ }, repo.created.MessagesDispatchModelConfig)
+}
+
+func TestAdminService_UpdateGroup_NormalizesMessagesDispatchModelConfig(t *testing.T) {
+ existingGroup := &Group{
+ ID: 1,
+ Name: "existing-group",
+ Platform: PlatformOpenAI,
+ Status: StatusActive,
+ }
+ repo := &groupRepoStubForAdmin{getByID: existingGroup}
+ svc := &adminServiceImpl{groupRepo: repo}
+
+ group, err := svc.UpdateGroup(context.Background(), 1, &UpdateGroupInput{
+ MessagesDispatchModelConfig: &OpenAIMessagesDispatchModelConfig{
+ SonnetMappedModel: " gpt-5.4-medium ",
+ ExactModelMappings: map[string]string{
+ " claude-haiku-4-5-20251001 ": " gpt-5.4-mini-high ",
+ },
+ },
+ })
+ require.NoError(t, err)
+ require.NotNil(t, group)
+ require.NotNil(t, repo.updated)
+ require.Equal(t, OpenAIMessagesDispatchModelConfig{
+ SonnetMappedModel: "gpt-5.4",
+ ExactModelMappings: map[string]string{
+ "claude-haiku-4-5-20251001": "gpt-5.4-mini",
+ },
+ }, repo.updated.MessagesDispatchModelConfig)
+}
+
+func TestAdminService_CreateGroup_ClearsMessagesDispatchFieldsForNonOpenAIPlatform(t *testing.T) {
+ repo := &groupRepoStubForAdmin{}
+ svc := &adminServiceImpl{groupRepo: repo}
+
+ group, err := svc.CreateGroup(context.Background(), &CreateGroupInput{
+ Name: "anthropic-group",
+ Description: "non-openai",
+ Platform: PlatformAnthropic,
+ RateMultiplier: 1.0,
+ AllowMessagesDispatch: true,
+ DefaultMappedModel: "gpt-5.4",
+ MessagesDispatchModelConfig: OpenAIMessagesDispatchModelConfig{
+ OpusMappedModel: "gpt-5.4",
+ },
+ })
+ require.NoError(t, err)
+ require.NotNil(t, group)
+ require.NotNil(t, repo.created)
+ require.False(t, repo.created.AllowMessagesDispatch)
+ require.Empty(t, repo.created.DefaultMappedModel)
+ require.Equal(t, OpenAIMessagesDispatchModelConfig{}, repo.created.MessagesDispatchModelConfig)
+}
+
+func TestAdminService_UpdateGroup_ClearsMessagesDispatchFieldsWhenPlatformChangesAwayFromOpenAI(t *testing.T) {
+ existingGroup := &Group{
+ ID: 1,
+ Name: "existing-openai-group",
+ Platform: PlatformOpenAI,
+ Status: StatusActive,
+ AllowMessagesDispatch: true,
+ DefaultMappedModel: "gpt-5.4",
+ MessagesDispatchModelConfig: OpenAIMessagesDispatchModelConfig{
+ SonnetMappedModel: "gpt-5.3-codex",
+ },
+ }
+ repo := &groupRepoStubForAdmin{getByID: existingGroup}
+ svc := &adminServiceImpl{groupRepo: repo}
+
+ group, err := svc.UpdateGroup(context.Background(), 1, &UpdateGroupInput{
+ Platform: PlatformAnthropic,
+ })
+ require.NoError(t, err)
+ require.NotNil(t, group)
+ require.NotNil(t, repo.updated)
+ require.Equal(t, PlatformAnthropic, repo.updated.Platform)
+ require.False(t, repo.updated.AllowMessagesDispatch)
+ require.Empty(t, repo.updated.DefaultMappedModel)
+ require.Equal(t, OpenAIMessagesDispatchModelConfig{}, repo.updated.MessagesDispatchModelConfig)
+}
+
func TestAdminService_ListGroups_WithSearch(t *testing.T) {
// 测试:
// 1. search 参数正常传递到 repository 层
@@ -258,7 +389,7 @@ func TestAdminService_ListGroups_WithSearch(t *testing.T) {
}
svc := &adminServiceImpl{groupRepo: repo}
- groups, total, err := svc.ListGroups(context.Background(), 1, 20, "", "", "alpha", nil)
+ groups, total, err := svc.ListGroups(context.Background(), 1, 20, "", "", "alpha", nil, "", "")
require.NoError(t, err)
require.Equal(t, int64(1), total)
require.Equal(t, []Group{{ID: 1, Name: "alpha"}}, groups)
@@ -276,7 +407,7 @@ func TestAdminService_ListGroups_WithSearch(t *testing.T) {
}
svc := &adminServiceImpl{groupRepo: repo}
- groups, total, err := svc.ListGroups(context.Background(), 2, 10, "", "", "", nil)
+ groups, total, err := svc.ListGroups(context.Background(), 2, 10, "", "", "", nil, "", "")
require.NoError(t, err)
require.Empty(t, groups)
require.Equal(t, int64(0), total)
@@ -295,7 +426,7 @@ func TestAdminService_ListGroups_WithSearch(t *testing.T) {
}
svc := &adminServiceImpl{groupRepo: repo}
- groups, total, err := svc.ListGroups(context.Background(), 3, 50, PlatformAntigravity, StatusActive, "beta", &isExclusive)
+ groups, total, err := svc.ListGroups(context.Background(), 3, 50, PlatformAntigravity, StatusActive, "beta", &isExclusive, "", "")
require.NoError(t, err)
require.Equal(t, int64(42), total)
require.Equal(t, []Group{{ID: 2, Name: "beta"}}, groups)
diff --git a/backend/internal/service/admin_service_list_users_test.go b/backend/internal/service/admin_service_list_users_test.go
index 37f348dfbd4a75d97143e2fe32454124e097b564..ceeb52c2944c830d82a071fecae79f4a00719ad2 100644
--- a/backend/internal/service/admin_service_list_users_test.go
+++ b/backend/internal/service/admin_service_list_users_test.go
@@ -13,11 +13,13 @@ import (
type userRepoStubForListUsers struct {
userRepoStub
- users []User
- err error
+ users []User
+ err error
+ listWithFiltersParams pagination.PaginationParams
}
func (s *userRepoStubForListUsers) ListWithFilters(_ context.Context, params pagination.PaginationParams, _ UserListFilters) ([]User, *pagination.PaginationResult, error) {
+ s.listWithFiltersParams = params
if s.err != nil {
return nil, nil, s.err
}
@@ -103,7 +105,7 @@ func TestAdminService_ListUsers_BatchRateFallbackToSingle(t *testing.T) {
userGroupRateRepo: rateRepo,
}
- users, total, err := svc.ListUsers(context.Background(), 1, 20, UserListFilters{})
+ users, total, err := svc.ListUsers(context.Background(), 1, 20, UserListFilters{}, "", "")
require.NoError(t, err)
require.Equal(t, int64(2), total)
require.Len(t, users, 2)
@@ -112,3 +114,19 @@ func TestAdminService_ListUsers_BatchRateFallbackToSingle(t *testing.T) {
require.Equal(t, 1.1, users[0].GroupRates[11])
require.Equal(t, 2.2, users[1].GroupRates[22])
}
+
+func TestAdminService_ListUsers_PassesSortParams(t *testing.T) {
+ userRepo := &userRepoStubForListUsers{
+ users: []User{{ID: 1, Email: "a@example.com"}},
+ }
+ svc := &adminServiceImpl{userRepo: userRepo}
+
+ _, _, err := svc.ListUsers(context.Background(), 2, 50, UserListFilters{}, "email", "ASC")
+ require.NoError(t, err)
+ require.Equal(t, pagination.PaginationParams{
+ Page: 2,
+ PageSize: 50,
+ SortBy: "email",
+ SortOrder: "ASC",
+ }, userRepo.listWithFiltersParams)
+}
diff --git a/backend/internal/service/admin_service_search_test.go b/backend/internal/service/admin_service_search_test.go
index eb213e6af6569b11b364abbe7b22356efc29affa..595e99e344f227e14a7e13e3a3e23da77d10ad46 100644
--- a/backend/internal/service/admin_service_search_test.go
+++ b/backend/internal/service/admin_service_search_test.go
@@ -170,13 +170,13 @@ func TestAdminService_ListAccounts_WithSearch(t *testing.T) {
}
svc := &adminServiceImpl{accountRepo: repo}
- accounts, total, err := svc.ListAccounts(context.Background(), 1, 20, PlatformGemini, AccountTypeOAuth, StatusActive, "acc", 0, "")
+ accounts, total, err := svc.ListAccounts(context.Background(), 1, 20, PlatformGemini, AccountTypeOAuth, StatusActive, "acc", 0, "", "name", "ASC")
require.NoError(t, err)
require.Equal(t, int64(10), total)
require.Equal(t, []Account{{ID: 1, Name: "acc"}}, accounts)
require.Equal(t, 1, repo.listWithFiltersCalls)
- require.Equal(t, pagination.PaginationParams{Page: 1, PageSize: 20}, repo.listWithFiltersParams)
+ require.Equal(t, pagination.PaginationParams{Page: 1, PageSize: 20, SortBy: "name", SortOrder: "ASC"}, repo.listWithFiltersParams)
require.Equal(t, PlatformGemini, repo.listWithFiltersPlatform)
require.Equal(t, AccountTypeOAuth, repo.listWithFiltersType)
require.Equal(t, StatusActive, repo.listWithFiltersStatus)
@@ -192,7 +192,7 @@ func TestAdminService_ListAccounts_WithPrivacyMode(t *testing.T) {
}
svc := &adminServiceImpl{accountRepo: repo}
- accounts, total, err := svc.ListAccounts(context.Background(), 1, 20, PlatformOpenAI, AccountTypeOAuth, StatusActive, "acc2", 0, PrivacyModeCFBlocked)
+ accounts, total, err := svc.ListAccounts(context.Background(), 1, 20, PlatformOpenAI, AccountTypeOAuth, StatusActive, "acc2", 0, PrivacyModeCFBlocked, "", "")
require.NoError(t, err)
require.Equal(t, int64(1), total)
require.Equal(t, []Account{{ID: 2, Name: "acc2"}}, accounts)
@@ -208,13 +208,13 @@ func TestAdminService_ListProxies_WithSearch(t *testing.T) {
}
svc := &adminServiceImpl{proxyRepo: repo}
- proxies, total, err := svc.ListProxies(context.Background(), 3, 50, "http", StatusActive, "p1")
+ proxies, total, err := svc.ListProxies(context.Background(), 3, 50, "http", StatusActive, "p1", "name", "ASC")
require.NoError(t, err)
require.Equal(t, int64(7), total)
require.Equal(t, []Proxy{{ID: 2, Name: "p1"}}, proxies)
require.Equal(t, 1, repo.listWithFiltersCalls)
- require.Equal(t, pagination.PaginationParams{Page: 3, PageSize: 50}, repo.listWithFiltersParams)
+ require.Equal(t, pagination.PaginationParams{Page: 3, PageSize: 50, SortBy: "name", SortOrder: "ASC"}, repo.listWithFiltersParams)
require.Equal(t, "http", repo.listWithFiltersProtocol)
require.Equal(t, StatusActive, repo.listWithFiltersStatus)
require.Equal(t, "p1", repo.listWithFiltersSearch)
@@ -229,13 +229,13 @@ func TestAdminService_ListProxiesWithAccountCount_WithSearch(t *testing.T) {
}
svc := &adminServiceImpl{proxyRepo: repo}
- proxies, total, err := svc.ListProxiesWithAccountCount(context.Background(), 2, 10, "socks5", StatusDisabled, "p2")
+ proxies, total, err := svc.ListProxiesWithAccountCount(context.Background(), 2, 10, "socks5", StatusDisabled, "p2", "account_count", "DESC")
require.NoError(t, err)
require.Equal(t, int64(9), total)
require.Equal(t, []ProxyWithAccountCount{{Proxy: Proxy{ID: 3, Name: "p2"}, AccountCount: 5}}, proxies)
require.Equal(t, 1, repo.listWithFiltersAndAccountCountCalls)
- require.Equal(t, pagination.PaginationParams{Page: 2, PageSize: 10}, repo.listWithFiltersAndAccountCountParams)
+ require.Equal(t, pagination.PaginationParams{Page: 2, PageSize: 10, SortBy: "account_count", SortOrder: "DESC"}, repo.listWithFiltersAndAccountCountParams)
require.Equal(t, "socks5", repo.listWithFiltersAndAccountCountProtocol)
require.Equal(t, StatusDisabled, repo.listWithFiltersAndAccountCountStatus)
require.Equal(t, "p2", repo.listWithFiltersAndAccountCountSearch)
@@ -250,13 +250,13 @@ func TestAdminService_ListRedeemCodes_WithSearch(t *testing.T) {
}
svc := &adminServiceImpl{redeemCodeRepo: repo}
- codes, total, err := svc.ListRedeemCodes(context.Background(), 1, 20, RedeemTypeBalance, StatusUnused, "ABC")
+ codes, total, err := svc.ListRedeemCodes(context.Background(), 1, 20, RedeemTypeBalance, StatusUnused, "ABC", "value", "ASC")
require.NoError(t, err)
require.Equal(t, int64(3), total)
require.Equal(t, []RedeemCode{{ID: 4, Code: "ABC"}}, codes)
require.Equal(t, 1, repo.listWithFiltersCalls)
- require.Equal(t, pagination.PaginationParams{Page: 1, PageSize: 20}, repo.listWithFiltersParams)
+ require.Equal(t, pagination.PaginationParams{Page: 1, PageSize: 20, SortBy: "value", SortOrder: "ASC"}, repo.listWithFiltersParams)
require.Equal(t, RedeemTypeBalance, repo.listWithFiltersType)
require.Equal(t, StatusUnused, repo.listWithFiltersStatus)
require.Equal(t, "ABC", repo.listWithFiltersSearch)
diff --git a/backend/internal/service/api_key_auth_cache.go b/backend/internal/service/api_key_auth_cache.go
index ad6ba0e930851c229dcef3935b3df9a161015b65..c2e96df13ae856e4ddc180eefc0d9f817e8119b6 100644
--- a/backend/internal/service/api_key_auth_cache.go
+++ b/backend/internal/service/api_key_auth_cache.go
@@ -4,6 +4,7 @@ import "time"
// APIKeyAuthSnapshot API Key 认证缓存快照(仅包含认证所需字段)
type APIKeyAuthSnapshot struct {
+ Version int `json:"version"`
APIKeyID int64 `json:"api_key_id"`
UserID int64 `json:"user_id"`
GroupID *int64 `json:"group_id,omitempty"`
@@ -63,8 +64,9 @@ type APIKeyAuthGroupSnapshot struct {
SupportedModelScopes []string `json:"supported_model_scopes,omitempty"`
// OpenAI Messages 调度配置(仅 openai 平台使用)
- AllowMessagesDispatch bool `json:"allow_messages_dispatch"`
- DefaultMappedModel string `json:"default_mapped_model,omitempty"`
+ AllowMessagesDispatch bool `json:"allow_messages_dispatch"`
+ DefaultMappedModel string `json:"default_mapped_model,omitempty"`
+ MessagesDispatchModelConfig OpenAIMessagesDispatchModelConfig `json:"messages_dispatch_model_config,omitempty"`
}
// APIKeyAuthCacheEntry 缓存条目,支持负缓存
diff --git a/backend/internal/service/api_key_auth_cache_impl.go b/backend/internal/service/api_key_auth_cache_impl.go
index 64a70e8cf4a8639017b1508bcf5e8276a428658a..8069ed4fe2877d393941264aaa98096c299b7769 100644
--- a/backend/internal/service/api_key_auth_cache_impl.go
+++ b/backend/internal/service/api_key_auth_cache_impl.go
@@ -13,6 +13,8 @@ import (
"github.com/dgraph-io/ristretto"
)
+const apiKeyAuthSnapshotVersion = 3
+
type apiKeyAuthCacheConfig struct {
l1Size int
l1TTL time.Duration
@@ -192,6 +194,9 @@ func (s *APIKeyService) applyAuthCacheEntry(key string, entry *APIKeyAuthCacheEn
if entry.Snapshot == nil {
return nil, false, nil
}
+ if entry.Snapshot.Version != apiKeyAuthSnapshotVersion {
+ return nil, false, nil
+ }
return s.snapshotToAPIKey(key, entry.Snapshot), true, nil
}
@@ -200,6 +205,7 @@ func (s *APIKeyService) snapshotFromAPIKey(apiKey *APIKey) *APIKeyAuthSnapshot {
return nil
}
snapshot := &APIKeyAuthSnapshot{
+ Version: apiKeyAuthSnapshotVersion,
APIKeyID: apiKey.ID,
UserID: apiKey.UserID,
GroupID: apiKey.GroupID,
@@ -243,6 +249,7 @@ func (s *APIKeyService) snapshotFromAPIKey(apiKey *APIKey) *APIKeyAuthSnapshot {
SupportedModelScopes: apiKey.Group.SupportedModelScopes,
AllowMessagesDispatch: apiKey.Group.AllowMessagesDispatch,
DefaultMappedModel: apiKey.Group.DefaultMappedModel,
+ MessagesDispatchModelConfig: apiKey.Group.MessagesDispatchModelConfig,
}
}
return snapshot
@@ -298,6 +305,7 @@ func (s *APIKeyService) snapshotToAPIKey(key string, snapshot *APIKeyAuthSnapsho
SupportedModelScopes: snapshot.Group.SupportedModelScopes,
AllowMessagesDispatch: snapshot.Group.AllowMessagesDispatch,
DefaultMappedModel: snapshot.Group.DefaultMappedModel,
+ MessagesDispatchModelConfig: snapshot.Group.MessagesDispatchModelConfig,
}
}
s.compileAPIKeyIPRules(apiKey)
diff --git a/backend/internal/service/api_key_service_cache_test.go b/backend/internal/service/api_key_service_cache_test.go
index 357f8deff7a89dff64c026a09705402384f6da8d..3c2f7dbb5c59edc9a87f7c2c9ab39733c36c6cdf 100644
--- a/backend/internal/service/api_key_service_cache_test.go
+++ b/backend/internal/service/api_key_service_cache_test.go
@@ -188,6 +188,7 @@ func TestAPIKeyService_GetByKey_UsesL2Cache(t *testing.T) {
groupID := int64(9)
cacheEntry := &APIKeyAuthCacheEntry{
Snapshot: &APIKeyAuthSnapshot{
+ Version: apiKeyAuthSnapshotVersion,
APIKeyID: 1,
UserID: 2,
GroupID: &groupID,
@@ -226,6 +227,129 @@ func TestAPIKeyService_GetByKey_UsesL2Cache(t *testing.T) {
require.Equal(t, map[string][]int64{"claude-opus-*": {1, 2}}, apiKey.Group.ModelRouting)
}
+func TestAPIKeyService_SnapshotRoundTrip_PreservesMessagesDispatchModelConfig(t *testing.T) {
+ svc := NewAPIKeyService(nil, nil, nil, nil, nil, nil, &config.Config{})
+ groupID := int64(9)
+ apiKey := &APIKey{
+ ID: 1,
+ UserID: 2,
+ GroupID: &groupID,
+ Key: "k-roundtrip",
+ Status: StatusActive,
+ User: &User{
+ ID: 2,
+ Status: StatusActive,
+ Role: RoleUser,
+ Balance: 10,
+ Concurrency: 3,
+ },
+ Group: &Group{
+ ID: groupID,
+ Name: "openai",
+ Platform: PlatformOpenAI,
+ Status: StatusActive,
+ SubscriptionType: SubscriptionTypeStandard,
+ RateMultiplier: 1,
+ AllowMessagesDispatch: true,
+ DefaultMappedModel: "gpt-5.4",
+ MessagesDispatchModelConfig: OpenAIMessagesDispatchModelConfig{
+ OpusMappedModel: "gpt-5.4-nano",
+ SonnetMappedModel: "gpt-5.3-codex",
+ HaikuMappedModel: "gpt-5.4-mini",
+ ExactModelMappings: map[string]string{
+ "claude-sonnet-4.5": "gpt-5.4-nano",
+ },
+ },
+ },
+ }
+
+ snapshot := svc.snapshotFromAPIKey(apiKey)
+ roundTrip := svc.snapshotToAPIKey(apiKey.Key, snapshot)
+
+ require.NotNil(t, roundTrip)
+ require.NotNil(t, roundTrip.Group)
+ require.Equal(t, apiKey.Group.MessagesDispatchModelConfig, roundTrip.Group.MessagesDispatchModelConfig)
+}
+
+func TestAPIKeyService_GetByKey_IgnoresLegacyAuthCacheSnapshotWithoutMessagesDispatchConfig(t *testing.T) {
+ cache := &authCacheStub{}
+ var repoCalls int32
+ repo := &authRepoStub{
+ getByKeyForAuth: func(ctx context.Context, key string) (*APIKey, error) {
+ atomic.AddInt32(&repoCalls, 1)
+ groupID := int64(9)
+ return &APIKey{
+ ID: 1,
+ UserID: 2,
+ GroupID: &groupID,
+ Status: StatusActive,
+ User: &User{
+ ID: 2,
+ Status: StatusActive,
+ Role: RoleUser,
+ Balance: 10,
+ Concurrency: 3,
+ },
+ Group: &Group{
+ ID: groupID,
+ Name: "openai",
+ Platform: PlatformOpenAI,
+ Status: StatusActive,
+ Hydrated: true,
+ SubscriptionType: SubscriptionTypeStandard,
+ RateMultiplier: 1,
+ AllowMessagesDispatch: true,
+ DefaultMappedModel: "gpt-5.4",
+ MessagesDispatchModelConfig: OpenAIMessagesDispatchModelConfig{
+ OpusMappedModel: "gpt-5.4-nano",
+ },
+ },
+ }, nil
+ },
+ }
+ cfg := &config.Config{
+ APIKeyAuth: config.APIKeyAuthCacheConfig{
+ L2TTLSeconds: 60,
+ },
+ }
+ svc := NewAPIKeyService(repo, nil, nil, nil, nil, cache, cfg)
+
+ groupID := int64(9)
+ cache.getAuthCache = func(ctx context.Context, key string) (*APIKeyAuthCacheEntry, error) {
+ return &APIKeyAuthCacheEntry{
+ Snapshot: &APIKeyAuthSnapshot{
+ APIKeyID: 1,
+ UserID: 2,
+ GroupID: &groupID,
+ Status: StatusActive,
+ User: APIKeyAuthUserSnapshot{
+ ID: 2,
+ Status: StatusActive,
+ Role: RoleUser,
+ Balance: 10,
+ Concurrency: 3,
+ },
+ Group: &APIKeyAuthGroupSnapshot{
+ ID: groupID,
+ Name: "openai",
+ Platform: PlatformOpenAI,
+ Status: StatusActive,
+ SubscriptionType: SubscriptionTypeStandard,
+ RateMultiplier: 1,
+ AllowMessagesDispatch: true,
+ DefaultMappedModel: "gpt-5.4",
+ },
+ },
+ }, nil
+ }
+
+ apiKey, err := svc.GetByKey(context.Background(), "k-legacy")
+ require.NoError(t, err)
+ require.Equal(t, int32(1), atomic.LoadInt32(&repoCalls))
+ require.NotNil(t, apiKey.Group)
+ require.Equal(t, "gpt-5.4-nano", apiKey.Group.MessagesDispatchModelConfig.OpusMappedModel)
+}
+
func TestAPIKeyService_GetByKey_NegativeCache(t *testing.T) {
cache := &authCacheStub{}
repo := &authRepoStub{
diff --git a/backend/internal/service/auth_service.go b/backend/internal/service/auth_service.go
index fcf39e1afd55cb8cdfb34b080e9d2e833a786a72..847f94a8edccbf73be5abc5a5169284952d81c3f 100644
--- a/backend/internal/service/auth_service.go
+++ b/backend/internal/service/auth_service.go
@@ -833,7 +833,8 @@ func randomHexString(byteLength int) (string, error) {
func isReservedEmail(email string) bool {
normalized := strings.ToLower(strings.TrimSpace(email))
- return strings.HasSuffix(normalized, LinuxDoConnectSyntheticEmailDomain)
+ return strings.HasSuffix(normalized, LinuxDoConnectSyntheticEmailDomain) ||
+ strings.HasSuffix(normalized, OIDCConnectSyntheticEmailDomain)
}
// GenerateToken 生成JWT access token
diff --git a/backend/internal/service/domain_constants.go b/backend/internal/service/domain_constants.go
index 92be3e06cfbfb6dc075ffb56212157dc24081972..68d7da3b45ffba61856b4c605ec583540852f6ed 100644
--- a/backend/internal/service/domain_constants.go
+++ b/backend/internal/service/domain_constants.go
@@ -71,6 +71,9 @@ const (
// LinuxDoConnectSyntheticEmailDomain 是 LinuxDo Connect 用户的合成邮箱后缀(RFC 保留域名)。
const LinuxDoConnectSyntheticEmailDomain = "@linuxdo-connect.invalid"
+// OIDCConnectSyntheticEmailDomain 是 OIDC 用户的合成邮箱后缀(RFC 保留域名)。
+const OIDCConnectSyntheticEmailDomain = "@oidc-connect.invalid"
+
// Setting keys
const (
// 注册设置
@@ -105,6 +108,30 @@ const (
SettingKeyLinuxDoConnectClientSecret = "linuxdo_connect_client_secret"
SettingKeyLinuxDoConnectRedirectURL = "linuxdo_connect_redirect_url"
+ // Generic OIDC OAuth 登录设置
+ SettingKeyOIDCConnectEnabled = "oidc_connect_enabled"
+ SettingKeyOIDCConnectProviderName = "oidc_connect_provider_name"
+ SettingKeyOIDCConnectClientID = "oidc_connect_client_id"
+ SettingKeyOIDCConnectClientSecret = "oidc_connect_client_secret"
+ SettingKeyOIDCConnectIssuerURL = "oidc_connect_issuer_url"
+ SettingKeyOIDCConnectDiscoveryURL = "oidc_connect_discovery_url"
+ SettingKeyOIDCConnectAuthorizeURL = "oidc_connect_authorize_url"
+ SettingKeyOIDCConnectTokenURL = "oidc_connect_token_url"
+ SettingKeyOIDCConnectUserInfoURL = "oidc_connect_userinfo_url"
+ SettingKeyOIDCConnectJWKSURL = "oidc_connect_jwks_url"
+ SettingKeyOIDCConnectScopes = "oidc_connect_scopes"
+ SettingKeyOIDCConnectRedirectURL = "oidc_connect_redirect_url"
+ SettingKeyOIDCConnectFrontendRedirectURL = "oidc_connect_frontend_redirect_url"
+ SettingKeyOIDCConnectTokenAuthMethod = "oidc_connect_token_auth_method"
+ SettingKeyOIDCConnectUsePKCE = "oidc_connect_use_pkce"
+ SettingKeyOIDCConnectValidateIDToken = "oidc_connect_validate_id_token"
+ SettingKeyOIDCConnectAllowedSigningAlgs = "oidc_connect_allowed_signing_algs"
+ SettingKeyOIDCConnectClockSkewSeconds = "oidc_connect_clock_skew_seconds"
+ SettingKeyOIDCConnectRequireEmailVerified = "oidc_connect_require_email_verified"
+ SettingKeyOIDCConnectUserInfoEmailPath = "oidc_connect_userinfo_email_path"
+ SettingKeyOIDCConnectUserInfoIDPath = "oidc_connect_userinfo_id_path"
+ SettingKeyOIDCConnectUserInfoUsernamePath = "oidc_connect_userinfo_username_path"
+
// OEM设置
SettingKeySiteName = "site_name" // 网站名称
SettingKeySiteLogo = "site_logo" // 网站Logo (base64)
@@ -116,6 +143,8 @@ const (
SettingKeyHideCcsImportButton = "hide_ccs_import_button" // 是否隐藏 API Keys 页面的导入 CCS 按钮
SettingKeyPurchaseSubscriptionEnabled = "purchase_subscription_enabled" // 是否展示"购买订阅"页面入口
SettingKeyPurchaseSubscriptionURL = "purchase_subscription_url" // "购买订阅"页面 URL(作为 iframe src)
+ SettingKeyTableDefaultPageSize = "table_default_page_size" // 表格默认每页条数
+ SettingKeyTablePageSizeOptions = "table_page_size_options" // 表格可选每页条数(JSON 数组)
SettingKeyCustomMenuItems = "custom_menu_items" // 自定义菜单项(JSON 数组)
SettingKeyCustomEndpoints = "custom_endpoints" // 自定义端点列表(JSON 数组)
diff --git a/backend/internal/service/gateway_service.go b/backend/internal/service/gateway_service.go
index a473364916c483fc9ef2ac416292803783a665da..8b0bdc2a58f6d68359cefd41c7587726c00ffcf0 100644
--- a/backend/internal/service/gateway_service.go
+++ b/backend/internal/service/gateway_service.go
@@ -1192,12 +1192,20 @@ func (s *GatewayService) SelectAccountForModelWithExclusions(ctx context.Context
// anthropic/gemini 分组支持混合调度(包含启用了 mixed_scheduling 的 antigravity 账户)
// 注意:强制平台模式不走混合调度
if (platform == PlatformAnthropic || platform == PlatformGemini) && !hasForcePlatform {
- return s.selectAccountWithMixedScheduling(ctx, groupID, sessionHash, requestedModel, excludedIDs, platform)
+ account, err := s.selectAccountWithMixedScheduling(ctx, groupID, sessionHash, requestedModel, excludedIDs, platform)
+ if err != nil {
+ return nil, err
+ }
+ return s.hydrateSelectedAccount(ctx, account)
}
// antigravity 分组、强制平台模式或无分组使用单平台选择
// 注意:强制平台模式也必须遵守分组限制,不再回退到全平台查询
- return s.selectAccountForModelWithPlatform(ctx, groupID, sessionHash, requestedModel, excludedIDs, platform)
+ account, err := s.selectAccountForModelWithPlatform(ctx, groupID, sessionHash, requestedModel, excludedIDs, platform)
+ if err != nil {
+ return nil, err
+ }
+ return s.hydrateSelectedAccount(ctx, account)
}
// SelectAccountWithLoadAwareness selects account with load-awareness and wait plan.
@@ -1273,11 +1281,7 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro
localExcluded[account.ID] = struct{}{} // 排除此账号
continue // 重新选择
}
- return &AccountSelectionResult{
- Account: account,
- Acquired: true,
- ReleaseFunc: result.ReleaseFunc,
- }, nil
+ return s.newSelectionResult(ctx, account, true, result.ReleaseFunc, nil)
}
// 对于等待计划的情况,也需要先检查会话限制
@@ -1289,26 +1293,20 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro
if stickyAccountID > 0 && stickyAccountID == account.ID && s.concurrencyService != nil {
waitingCount, _ := s.concurrencyService.GetAccountWaitingCount(ctx, account.ID)
if waitingCount < cfg.StickySessionMaxWaiting {
- return &AccountSelectionResult{
- Account: account,
- WaitPlan: &AccountWaitPlan{
- AccountID: account.ID,
- MaxConcurrency: account.Concurrency,
- Timeout: cfg.StickySessionWaitTimeout,
- MaxWaiting: cfg.StickySessionMaxWaiting,
- },
- }, nil
+ return s.newSelectionResult(ctx, account, false, nil, &AccountWaitPlan{
+ AccountID: account.ID,
+ MaxConcurrency: account.Concurrency,
+ Timeout: cfg.StickySessionWaitTimeout,
+ MaxWaiting: cfg.StickySessionMaxWaiting,
+ })
}
}
- return &AccountSelectionResult{
- Account: account,
- WaitPlan: &AccountWaitPlan{
- AccountID: account.ID,
- MaxConcurrency: account.Concurrency,
- Timeout: cfg.FallbackWaitTimeout,
- MaxWaiting: cfg.FallbackMaxWaiting,
- },
- }, nil
+ return s.newSelectionResult(ctx, account, false, nil, &AccountWaitPlan{
+ AccountID: account.ID,
+ MaxConcurrency: account.Concurrency,
+ Timeout: cfg.FallbackWaitTimeout,
+ MaxWaiting: cfg.FallbackMaxWaiting,
+ })
}
}
@@ -1455,11 +1453,7 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro
if s.debugModelRoutingEnabled() {
logger.LegacyPrintf("service.gateway", "[ModelRoutingDebug] routed sticky hit: group_id=%v model=%s session=%s account=%d", derefGroupID(groupID), requestedModel, shortSessionHash(sessionHash), stickyAccountID)
}
- return &AccountSelectionResult{
- Account: stickyAccount,
- Acquired: true,
- ReleaseFunc: result.ReleaseFunc,
- }, nil
+ return s.newSelectionResult(ctx, stickyAccount, true, result.ReleaseFunc, nil)
}
}
@@ -1570,11 +1564,7 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro
if s.debugModelRoutingEnabled() {
logger.LegacyPrintf("service.gateway", "[ModelRoutingDebug] routed select: group_id=%v model=%s session=%s account=%d", derefGroupID(groupID), requestedModel, shortSessionHash(sessionHash), item.account.ID)
}
- return &AccountSelectionResult{
- Account: item.account,
- Acquired: true,
- ReleaseFunc: result.ReleaseFunc,
- }, nil
+ return s.newSelectionResult(ctx, item.account, true, result.ReleaseFunc, nil)
}
}
@@ -1587,15 +1577,12 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro
if s.debugModelRoutingEnabled() {
logger.LegacyPrintf("service.gateway", "[ModelRoutingDebug] routed wait: group_id=%v model=%s session=%s account=%d", derefGroupID(groupID), requestedModel, shortSessionHash(sessionHash), item.account.ID)
}
- return &AccountSelectionResult{
- Account: item.account,
- WaitPlan: &AccountWaitPlan{
- AccountID: item.account.ID,
- MaxConcurrency: item.account.Concurrency,
- Timeout: cfg.StickySessionWaitTimeout,
- MaxWaiting: cfg.StickySessionMaxWaiting,
- },
- }, nil
+ return s.newSelectionResult(ctx, item.account, false, nil, &AccountWaitPlan{
+ AccountID: item.account.ID,
+ MaxConcurrency: item.account.Concurrency,
+ Timeout: cfg.StickySessionWaitTimeout,
+ MaxWaiting: cfg.StickySessionMaxWaiting,
+ })
}
// 所有路由账号会话限制都已满,继续到 Layer 2 回退
}
@@ -1631,11 +1618,10 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro
if !s.checkAndRegisterSession(ctx, account, sessionHash) {
result.ReleaseFunc() // 释放槽位,继续到 Layer 2
} else {
- return &AccountSelectionResult{
- Account: account,
- Acquired: true,
- ReleaseFunc: result.ReleaseFunc,
- }, nil
+ if s.cache != nil {
+ _ = s.cache.RefreshSessionTTL(ctx, derefGroupID(groupID), sessionHash, stickySessionTTL)
+ }
+ return s.newSelectionResult(ctx, account, true, result.ReleaseFunc, nil)
}
}
@@ -1647,15 +1633,12 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro
// 会话限制已满,继续到 Layer 2
// Session limit full, continue to Layer 2
} else {
- return &AccountSelectionResult{
- Account: account,
- WaitPlan: &AccountWaitPlan{
- AccountID: accountID,
- MaxConcurrency: account.Concurrency,
- Timeout: cfg.StickySessionWaitTimeout,
- MaxWaiting: cfg.StickySessionMaxWaiting,
- },
- }, nil
+ return s.newSelectionResult(ctx, account, false, nil, &AccountWaitPlan{
+ AccountID: accountID,
+ MaxConcurrency: account.Concurrency,
+ Timeout: cfg.StickySessionWaitTimeout,
+ MaxWaiting: cfg.StickySessionMaxWaiting,
+ })
}
}
}
@@ -1714,7 +1697,9 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro
loadMap, err := s.concurrencyService.GetAccountsLoadBatch(ctx, accountLoads)
if err != nil {
- if result, ok := s.tryAcquireByLegacyOrder(ctx, candidates, groupID, sessionHash, preferOAuth); ok {
+ if result, ok, legacyErr := s.tryAcquireByLegacyOrder(ctx, candidates, groupID, sessionHash, preferOAuth); legacyErr != nil {
+ return nil, legacyErr
+ } else if ok {
return result, nil
}
} else {
@@ -1753,11 +1738,7 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro
if sessionHash != "" && s.cache != nil {
_ = s.cache.SetSessionAccountID(ctx, derefGroupID(groupID), sessionHash, selected.account.ID, stickySessionTTL)
}
- return &AccountSelectionResult{
- Account: selected.account,
- Acquired: true,
- ReleaseFunc: result.ReleaseFunc,
- }, nil
+ return s.newSelectionResult(ctx, selected.account, true, result.ReleaseFunc, nil)
}
}
@@ -1780,20 +1761,17 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro
if !s.checkAndRegisterSession(ctx, acc, sessionHash) {
continue // 会话限制已满,尝试下一个账号
}
- return &AccountSelectionResult{
- Account: acc,
- WaitPlan: &AccountWaitPlan{
- AccountID: acc.ID,
- MaxConcurrency: acc.Concurrency,
- Timeout: cfg.FallbackWaitTimeout,
- MaxWaiting: cfg.FallbackMaxWaiting,
- },
- }, nil
+ return s.newSelectionResult(ctx, acc, false, nil, &AccountWaitPlan{
+ AccountID: acc.ID,
+ MaxConcurrency: acc.Concurrency,
+ Timeout: cfg.FallbackWaitTimeout,
+ MaxWaiting: cfg.FallbackMaxWaiting,
+ })
}
return nil, ErrNoAvailableAccounts
}
-func (s *GatewayService) tryAcquireByLegacyOrder(ctx context.Context, candidates []*Account, groupID *int64, sessionHash string, preferOAuth bool) (*AccountSelectionResult, bool) {
+func (s *GatewayService) tryAcquireByLegacyOrder(ctx context.Context, candidates []*Account, groupID *int64, sessionHash string, preferOAuth bool) (*AccountSelectionResult, bool, error) {
ordered := append([]*Account(nil), candidates...)
sortAccountsByPriorityAndLastUsed(ordered, preferOAuth)
@@ -1808,15 +1786,15 @@ func (s *GatewayService) tryAcquireByLegacyOrder(ctx context.Context, candidates
if sessionHash != "" && s.cache != nil {
_ = s.cache.SetSessionAccountID(ctx, derefGroupID(groupID), sessionHash, acc.ID, stickySessionTTL)
}
- return &AccountSelectionResult{
- Account: acc,
- Acquired: true,
- ReleaseFunc: result.ReleaseFunc,
- }, true
+ selection, err := s.newSelectionResult(ctx, acc, true, result.ReleaseFunc, nil)
+ if err != nil {
+ return nil, false, err
+ }
+ return selection, true, nil
}
}
- return nil, false
+ return nil, false, nil
}
func (s *GatewayService) schedulingConfig() config.GatewaySchedulingConfig {
@@ -2431,6 +2409,33 @@ func (s *GatewayService) getSchedulableAccount(ctx context.Context, accountID in
return s.accountRepo.GetByID(ctx, accountID)
}
+func (s *GatewayService) hydrateSelectedAccount(ctx context.Context, account *Account) (*Account, error) {
+ if account == nil || s.schedulerSnapshot == nil {
+ return account, nil
+ }
+ hydrated, err := s.schedulerSnapshot.GetAccount(ctx, account.ID)
+ if err != nil {
+ return nil, err
+ }
+ if hydrated == nil {
+ return nil, fmt.Errorf("selected gateway account %d not found during hydration", account.ID)
+ }
+ return hydrated, nil
+}
+
+func (s *GatewayService) newSelectionResult(ctx context.Context, account *Account, acquired bool, release func(), waitPlan *AccountWaitPlan) (*AccountSelectionResult, error) {
+ hydrated, err := s.hydrateSelectedAccount(ctx, account)
+ if err != nil {
+ return nil, err
+ }
+ return &AccountSelectionResult{
+ Account: hydrated,
+ Acquired: acquired,
+ ReleaseFunc: release,
+ WaitPlan: waitPlan,
+ }, nil
+}
+
// filterByMinPriority 过滤出优先级最小的账号集合
func filterByMinPriority(accounts []accountWithLoad) []accountWithLoad {
if len(accounts) == 0 {
diff --git a/backend/internal/service/gemini_messages_compat_service.go b/backend/internal/service/gemini_messages_compat_service.go
index 32bf21c01f19cd5e21f5aa1a6dce544b25bdc196..5a9490f36d415f585774c66cb4779107eafaa955 100644
--- a/backend/internal/service/gemini_messages_compat_service.go
+++ b/backend/internal/service/gemini_messages_compat_service.go
@@ -137,7 +137,7 @@ func (s *GeminiMessagesCompatService) SelectAccountForModelWithExclusions(ctx co
_ = s.cache.SetSessionAccountID(ctx, derefGroupID(groupID), cacheKey, selected.ID, geminiStickySessionTTL)
}
- return selected, nil
+ return s.hydrateSelectedAccount(ctx, selected)
}
// resolvePlatformAndSchedulingMode 解析目标平台和调度模式。
@@ -416,6 +416,20 @@ func (s *GeminiMessagesCompatService) getSchedulableAccount(ctx context.Context,
return s.accountRepo.GetByID(ctx, accountID)
}
+func (s *GeminiMessagesCompatService) hydrateSelectedAccount(ctx context.Context, account *Account) (*Account, error) {
+ if account == nil || s.schedulerSnapshot == nil {
+ return account, nil
+ }
+ hydrated, err := s.schedulerSnapshot.GetAccount(ctx, account.ID)
+ if err != nil {
+ return nil, err
+ }
+ if hydrated == nil {
+ return nil, fmt.Errorf("selected gemini account %d not found during hydration", account.ID)
+ }
+ return hydrated, nil
+}
+
func (s *GeminiMessagesCompatService) listSchedulableAccountsOnce(ctx context.Context, groupID *int64, platform string, hasForcePlatform bool) ([]Account, error) {
if s.schedulerSnapshot != nil {
accounts, _, err := s.schedulerSnapshot.ListSchedulableAccounts(ctx, groupID, platform, hasForcePlatform)
@@ -546,7 +560,7 @@ func (s *GeminiMessagesCompatService) SelectAccountForAIStudioEndpoints(ctx cont
if selected == nil {
return nil, errors.New("no available Gemini accounts")
}
- return selected, nil
+ return s.hydrateSelectedAccount(ctx, selected)
}
func (s *GeminiMessagesCompatService) Forward(ctx context.Context, c *gin.Context, account *Account, body []byte) (*ForwardResult, error) {
diff --git a/backend/internal/service/group.go b/backend/internal/service/group.go
index d59af9e1c09e472736c086db2407832dc8a0fe2b..12262613571e347d00a9ed1f4dfbffbd6649c57a 100644
--- a/backend/internal/service/group.go
+++ b/backend/internal/service/group.go
@@ -3,8 +3,12 @@ package service
import (
"strings"
"time"
+
+ "github.com/Wei-Shaw/sub2api/internal/domain"
)
+type OpenAIMessagesDispatchModelConfig = domain.OpenAIMessagesDispatchModelConfig
+
type Group struct {
ID int64
Name string
@@ -49,10 +53,11 @@ type Group struct {
SortOrder int
// OpenAI Messages 调度配置(仅 openai 平台使用)
- AllowMessagesDispatch bool
- RequireOAuthOnly bool // 仅允许非 apikey 类型账号关联(OpenAI/Antigravity/Anthropic/Gemini)
- RequirePrivacySet bool // 调度时仅允许 privacy 已成功设置的账号(OpenAI/Antigravity/Anthropic/Gemini)
- DefaultMappedModel string
+ AllowMessagesDispatch bool
+ RequireOAuthOnly bool // 仅允许非 apikey 类型账号关联(OpenAI/Antigravity/Anthropic/Gemini)
+ RequirePrivacySet bool // 调度时仅允许 privacy 已成功设置的账号(OpenAI/Antigravity/Anthropic/Gemini)
+ DefaultMappedModel string
+ MessagesDispatchModelConfig OpenAIMessagesDispatchModelConfig
CreatedAt time.Time
UpdatedAt time.Time
diff --git a/backend/internal/service/openai_codex_instructions_template.go b/backend/internal/service/openai_codex_instructions_template.go
new file mode 100644
index 0000000000000000000000000000000000000000..5588c73cbbaa5eb8b548ceaa7b4272e36a8c3f7d
--- /dev/null
+++ b/backend/internal/service/openai_codex_instructions_template.go
@@ -0,0 +1,55 @@
+package service
+
+import (
+ "bytes"
+ "fmt"
+ "strings"
+ "text/template"
+)
+
+type forcedCodexInstructionsTemplateData struct {
+ ExistingInstructions string
+ OriginalModel string
+ NormalizedModel string
+ BillingModel string
+ UpstreamModel string
+}
+
+func applyForcedCodexInstructionsTemplate(
+ reqBody map[string]any,
+ templateText string,
+ data forcedCodexInstructionsTemplateData,
+) (bool, error) {
+ rendered, err := renderForcedCodexInstructionsTemplate(templateText, data)
+ if err != nil {
+ return false, err
+ }
+ if rendered == "" {
+ return false, nil
+ }
+
+ existing, _ := reqBody["instructions"].(string)
+ if strings.TrimSpace(existing) == rendered {
+ return false, nil
+ }
+
+ reqBody["instructions"] = rendered
+ return true, nil
+}
+
+func renderForcedCodexInstructionsTemplate(
+ templateText string,
+ data forcedCodexInstructionsTemplateData,
+) (string, error) {
+ tmpl, err := template.New("forced_codex_instructions").Option("missingkey=zero").Parse(templateText)
+ if err != nil {
+ return "", fmt.Errorf("parse forced codex instructions template: %w", err)
+ }
+
+ var buf bytes.Buffer
+ if err := tmpl.Execute(&buf, data); err != nil {
+ return "", fmt.Errorf("render forced codex instructions template: %w", err)
+ }
+
+ return strings.TrimSpace(buf.String()), nil
+}
diff --git a/backend/internal/service/openai_compat_model_test.go b/backend/internal/service/openai_compat_model_test.go
index 32c646d490b57d5d501f1db1689c597b1f68bd8e..4396c15fd0b2c239202533fcb10fd4e8a43487e4 100644
--- a/backend/internal/service/openai_compat_model_test.go
+++ b/backend/internal/service/openai_compat_model_test.go
@@ -6,9 +6,12 @@ import (
"io"
"net/http"
"net/http/httptest"
+ "os"
+ "path/filepath"
"strings"
"testing"
+ "github.com/Wei-Shaw/sub2api/internal/config"
"github.com/Wei-Shaw/sub2api/internal/pkg/apicompat"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/require"
@@ -127,3 +130,101 @@ func TestForwardAsAnthropic_NormalizesRoutingAndEffortForGpt54XHigh(t *testing.T
t.Logf("upstream body: %s", string(upstream.lastBody))
t.Logf("response body: %s", rec.Body.String())
}
+
+func TestForwardAsAnthropic_ForcedCodexInstructionsTemplatePrependsRenderedInstructions(t *testing.T) {
+ t.Parallel()
+ gin.SetMode(gin.TestMode)
+
+ templateDir := t.TempDir()
+ templatePath := filepath.Join(templateDir, "codex-instructions.md.tmpl")
+ require.NoError(t, os.WriteFile(templatePath, []byte("server-prefix\n\n{{ .ExistingInstructions }}"), 0o644))
+
+ rec := httptest.NewRecorder()
+ c, _ := gin.CreateTestContext(rec)
+ body := []byte(`{"model":"gpt-5.4","max_tokens":16,"system":"client-system","messages":[{"role":"user","content":"hello"}],"stream":false}`)
+ c.Request = httptest.NewRequest(http.MethodPost, "/v1/messages", bytes.NewReader(body))
+ c.Request.Header.Set("Content-Type", "application/json")
+
+ upstreamBody := strings.Join([]string{
+ `data: {"type":"response.completed","response":{"id":"resp_1","object":"response","model":"gpt-5.4","status":"completed","output":[{"type":"message","id":"msg_1","role":"assistant","status":"completed","content":[{"type":"output_text","text":"ok"}]}],"usage":{"input_tokens":5,"output_tokens":2,"total_tokens":7}}}`,
+ "",
+ "data: [DONE]",
+ "",
+ }, "\n")
+ upstream := &httpUpstreamRecorder{resp: &http.Response{
+ StatusCode: http.StatusOK,
+ Header: http.Header{"Content-Type": []string{"text/event-stream"}, "x-request-id": []string{"rid_forced"}},
+ Body: io.NopCloser(strings.NewReader(upstreamBody)),
+ }}
+
+ svc := &OpenAIGatewayService{
+ cfg: &config.Config{Gateway: config.GatewayConfig{
+ ForcedCodexInstructionsTemplateFile: templatePath,
+ ForcedCodexInstructionsTemplate: "server-prefix\n\n{{ .ExistingInstructions }}",
+ }},
+ httpUpstream: upstream,
+ }
+ account := &Account{
+ ID: 1,
+ Name: "openai-oauth",
+ Platform: PlatformOpenAI,
+ Type: AccountTypeOAuth,
+ Concurrency: 1,
+ Credentials: map[string]any{
+ "access_token": "oauth-token",
+ "chatgpt_account_id": "chatgpt-acc",
+ },
+ }
+
+ result, err := svc.ForwardAsAnthropic(context.Background(), c, account, body, "", "gpt-5.1")
+ require.NoError(t, err)
+ require.NotNil(t, result)
+ require.Equal(t, "server-prefix\n\nclient-system", gjson.GetBytes(upstream.lastBody, "instructions").String())
+}
+
+func TestForwardAsAnthropic_ForcedCodexInstructionsTemplateUsesCachedTemplateContent(t *testing.T) {
+ t.Parallel()
+ gin.SetMode(gin.TestMode)
+
+ rec := httptest.NewRecorder()
+ c, _ := gin.CreateTestContext(rec)
+ body := []byte(`{"model":"gpt-5.4","max_tokens":16,"system":"client-system","messages":[{"role":"user","content":"hello"}],"stream":false}`)
+ c.Request = httptest.NewRequest(http.MethodPost, "/v1/messages", bytes.NewReader(body))
+ c.Request.Header.Set("Content-Type", "application/json")
+
+ upstreamBody := strings.Join([]string{
+ `data: {"type":"response.completed","response":{"id":"resp_1","object":"response","model":"gpt-5.4","status":"completed","output":[{"type":"message","id":"msg_1","role":"assistant","status":"completed","content":[{"type":"output_text","text":"ok"}]}],"usage":{"input_tokens":5,"output_tokens":2,"total_tokens":7}}}`,
+ "",
+ "data: [DONE]",
+ "",
+ }, "\n")
+ upstream := &httpUpstreamRecorder{resp: &http.Response{
+ StatusCode: http.StatusOK,
+ Header: http.Header{"Content-Type": []string{"text/event-stream"}, "x-request-id": []string{"rid_forced_cached"}},
+ Body: io.NopCloser(strings.NewReader(upstreamBody)),
+ }}
+
+ svc := &OpenAIGatewayService{
+ cfg: &config.Config{Gateway: config.GatewayConfig{
+ ForcedCodexInstructionsTemplateFile: "/path/that/should/not/be/read.tmpl",
+ ForcedCodexInstructionsTemplate: "cached-prefix\n\n{{ .ExistingInstructions }}",
+ }},
+ httpUpstream: upstream,
+ }
+ account := &Account{
+ ID: 1,
+ Name: "openai-oauth",
+ Platform: PlatformOpenAI,
+ Type: AccountTypeOAuth,
+ Concurrency: 1,
+ Credentials: map[string]any{
+ "access_token": "oauth-token",
+ "chatgpt_account_id": "chatgpt-acc",
+ },
+ }
+
+ result, err := svc.ForwardAsAnthropic(context.Background(), c, account, body, "", "gpt-5.1")
+ require.NoError(t, err)
+ require.NotNil(t, result)
+ require.Equal(t, "cached-prefix\n\nclient-system", gjson.GetBytes(upstream.lastBody, "instructions").String())
+}
diff --git a/backend/internal/service/openai_gateway_messages.go b/backend/internal/service/openai_gateway_messages.go
index 6f53928b7d2da6ada0f8c92c2a4acc82f7015350..7a4862d33510dff9da81666a98225c828ab07ff6 100644
--- a/backend/internal/service/openai_gateway_messages.go
+++ b/backend/internal/service/openai_gateway_messages.go
@@ -86,6 +86,24 @@ func (s *OpenAIGatewayService) ForwardAsAnthropic(
return nil, fmt.Errorf("unmarshal for codex transform: %w", err)
}
codexResult := applyCodexOAuthTransform(reqBody, false, false)
+ forcedTemplateText := ""
+ if s.cfg != nil {
+ forcedTemplateText = s.cfg.Gateway.ForcedCodexInstructionsTemplate
+ }
+ templateUpstreamModel := upstreamModel
+ if codexResult.NormalizedModel != "" {
+ templateUpstreamModel = codexResult.NormalizedModel
+ }
+ existingInstructions, _ := reqBody["instructions"].(string)
+ if _, err := applyForcedCodexInstructionsTemplate(reqBody, forcedTemplateText, forcedCodexInstructionsTemplateData{
+ ExistingInstructions: strings.TrimSpace(existingInstructions),
+ OriginalModel: originalModel,
+ NormalizedModel: normalizedModel,
+ BillingModel: billingModel,
+ UpstreamModel: templateUpstreamModel,
+ }); err != nil {
+ return nil, err
+ }
if codexResult.NormalizedModel != "" {
upstreamModel = codexResult.NormalizedModel
}
diff --git a/backend/internal/service/openai_gateway_service.go b/backend/internal/service/openai_gateway_service.go
index 2623d773770432e4d52c1cacc975d54a8502a5a6..dbc538692036f25d4a59b0ccc7456cc636c9dc72 100644
--- a/backend/internal/service/openai_gateway_service.go
+++ b/backend/internal/service/openai_gateway_service.go
@@ -1243,7 +1243,7 @@ func (s *OpenAIGatewayService) selectAccountForModelWithExclusions(ctx context.C
_ = s.setStickySessionAccountID(ctx, groupID, sessionHash, selected.ID, openaiStickySessionTTL)
}
- return selected, nil
+ return s.hydrateSelectedAccount(ctx, selected)
}
// tryStickySessionHit 尝试从粘性会话获取账号。
@@ -1408,35 +1408,25 @@ func (s *OpenAIGatewayService) SelectAccountWithLoadAwareness(ctx context.Contex
}
result, err := s.tryAcquireAccountSlot(ctx, account.ID, account.Concurrency)
if err == nil && result.Acquired {
- return &AccountSelectionResult{
- Account: account,
- Acquired: true,
- ReleaseFunc: result.ReleaseFunc,
- }, nil
+ return s.newSelectionResult(ctx, account, true, result.ReleaseFunc, nil)
}
if stickyAccountID > 0 && stickyAccountID == account.ID && s.concurrencyService != nil {
waitingCount, _ := s.concurrencyService.GetAccountWaitingCount(ctx, account.ID)
if waitingCount < cfg.StickySessionMaxWaiting {
- return &AccountSelectionResult{
- Account: account,
- WaitPlan: &AccountWaitPlan{
- AccountID: account.ID,
- MaxConcurrency: account.Concurrency,
- Timeout: cfg.StickySessionWaitTimeout,
- MaxWaiting: cfg.StickySessionMaxWaiting,
- },
- }, nil
+ return s.newSelectionResult(ctx, account, false, nil, &AccountWaitPlan{
+ AccountID: account.ID,
+ MaxConcurrency: account.Concurrency,
+ Timeout: cfg.StickySessionWaitTimeout,
+ MaxWaiting: cfg.StickySessionMaxWaiting,
+ })
}
}
- return &AccountSelectionResult{
- Account: account,
- WaitPlan: &AccountWaitPlan{
- AccountID: account.ID,
- MaxConcurrency: account.Concurrency,
- Timeout: cfg.FallbackWaitTimeout,
- MaxWaiting: cfg.FallbackMaxWaiting,
- },
- }, nil
+ return s.newSelectionResult(ctx, account, false, nil, &AccountWaitPlan{
+ AccountID: account.ID,
+ MaxConcurrency: account.Concurrency,
+ Timeout: cfg.FallbackWaitTimeout,
+ MaxWaiting: cfg.FallbackMaxWaiting,
+ })
}
accounts, err := s.listSchedulableAccounts(ctx, groupID)
@@ -1476,24 +1466,17 @@ func (s *OpenAIGatewayService) SelectAccountWithLoadAwareness(ctx context.Contex
result, err := s.tryAcquireAccountSlot(ctx, accountID, account.Concurrency)
if err == nil && result.Acquired {
_ = s.refreshStickySessionTTL(ctx, groupID, sessionHash, openaiStickySessionTTL)
- return &AccountSelectionResult{
- Account: account,
- Acquired: true,
- ReleaseFunc: result.ReleaseFunc,
- }, nil
+ return s.newSelectionResult(ctx, account, true, result.ReleaseFunc, nil)
}
waitingCount, _ := s.concurrencyService.GetAccountWaitingCount(ctx, accountID)
if waitingCount < cfg.StickySessionMaxWaiting {
- return &AccountSelectionResult{
- Account: account,
- WaitPlan: &AccountWaitPlan{
- AccountID: accountID,
- MaxConcurrency: account.Concurrency,
- Timeout: cfg.StickySessionWaitTimeout,
- MaxWaiting: cfg.StickySessionMaxWaiting,
- },
- }, nil
+ return s.newSelectionResult(ctx, account, false, nil, &AccountWaitPlan{
+ AccountID: accountID,
+ MaxConcurrency: account.Concurrency,
+ Timeout: cfg.StickySessionWaitTimeout,
+ MaxWaiting: cfg.StickySessionMaxWaiting,
+ })
}
}
}
@@ -1552,11 +1535,7 @@ func (s *OpenAIGatewayService) SelectAccountWithLoadAwareness(ctx context.Contex
if sessionHash != "" {
_ = s.setStickySessionAccountID(ctx, groupID, sessionHash, fresh.ID, openaiStickySessionTTL)
}
- return &AccountSelectionResult{
- Account: fresh,
- Acquired: true,
- ReleaseFunc: result.ReleaseFunc,
- }, nil
+ return s.newSelectionResult(ctx, fresh, true, result.ReleaseFunc, nil)
}
}
} else {
@@ -1609,11 +1588,7 @@ func (s *OpenAIGatewayService) SelectAccountWithLoadAwareness(ctx context.Contex
if sessionHash != "" {
_ = s.setStickySessionAccountID(ctx, groupID, sessionHash, fresh.ID, openaiStickySessionTTL)
}
- return &AccountSelectionResult{
- Account: fresh,
- Acquired: true,
- ReleaseFunc: result.ReleaseFunc,
- }, nil
+ return s.newSelectionResult(ctx, fresh, true, result.ReleaseFunc, nil)
}
}
}
@@ -1629,15 +1604,12 @@ func (s *OpenAIGatewayService) SelectAccountWithLoadAwareness(ctx context.Contex
if needsUpstreamCheck && s.isUpstreamModelRestrictedByChannel(ctx, *groupID, fresh, requestedModel) {
continue
}
- return &AccountSelectionResult{
- Account: fresh,
- WaitPlan: &AccountWaitPlan{
- AccountID: fresh.ID,
- MaxConcurrency: fresh.Concurrency,
- Timeout: cfg.FallbackWaitTimeout,
- MaxWaiting: cfg.FallbackMaxWaiting,
- },
- }, nil
+ return s.newSelectionResult(ctx, fresh, false, nil, &AccountWaitPlan{
+ AccountID: fresh.ID,
+ MaxConcurrency: fresh.Concurrency,
+ Timeout: cfg.FallbackWaitTimeout,
+ MaxWaiting: cfg.FallbackMaxWaiting,
+ })
}
return nil, ErrNoAvailableAccounts
@@ -1732,6 +1704,33 @@ func (s *OpenAIGatewayService) getSchedulableAccount(ctx context.Context, accoun
return account, nil
}
+func (s *OpenAIGatewayService) hydrateSelectedAccount(ctx context.Context, account *Account) (*Account, error) {
+ if account == nil || s.schedulerSnapshot == nil {
+ return account, nil
+ }
+ hydrated, err := s.schedulerSnapshot.GetAccount(ctx, account.ID)
+ if err != nil {
+ return nil, err
+ }
+ if hydrated == nil {
+ return nil, fmt.Errorf("selected openai account %d not found during hydration", account.ID)
+ }
+ return hydrated, nil
+}
+
+func (s *OpenAIGatewayService) newSelectionResult(ctx context.Context, account *Account, acquired bool, release func(), waitPlan *AccountWaitPlan) (*AccountSelectionResult, error) {
+ hydrated, err := s.hydrateSelectedAccount(ctx, account)
+ if err != nil {
+ return nil, err
+ }
+ return &AccountSelectionResult{
+ Account: hydrated,
+ Acquired: acquired,
+ ReleaseFunc: release,
+ WaitPlan: waitPlan,
+ }, nil
+}
+
func (s *OpenAIGatewayService) schedulingConfig() config.GatewaySchedulingConfig {
if s.cfg != nil {
return s.cfg.Gateway.Scheduling
diff --git a/backend/internal/service/openai_messages_dispatch.go b/backend/internal/service/openai_messages_dispatch.go
new file mode 100644
index 0000000000000000000000000000000000000000..f2c1ad3c1baffe3eafafed4b4c6b9e83bf1a73a6
--- /dev/null
+++ b/backend/internal/service/openai_messages_dispatch.go
@@ -0,0 +1,100 @@
+package service
+
+import "strings"
+
+const (
+ defaultOpenAIMessagesDispatchOpusMappedModel = "gpt-5.4"
+ defaultOpenAIMessagesDispatchSonnetMappedModel = "gpt-5.3-codex"
+ defaultOpenAIMessagesDispatchHaikuMappedModel = "gpt-5.4-mini"
+)
+
+func normalizeOpenAIMessagesDispatchMappedModel(model string) string {
+ model = NormalizeOpenAICompatRequestedModel(strings.TrimSpace(model))
+ return strings.TrimSpace(model)
+}
+
+func normalizeOpenAIMessagesDispatchModelConfig(cfg OpenAIMessagesDispatchModelConfig) OpenAIMessagesDispatchModelConfig {
+ out := OpenAIMessagesDispatchModelConfig{
+ OpusMappedModel: normalizeOpenAIMessagesDispatchMappedModel(cfg.OpusMappedModel),
+ SonnetMappedModel: normalizeOpenAIMessagesDispatchMappedModel(cfg.SonnetMappedModel),
+ HaikuMappedModel: normalizeOpenAIMessagesDispatchMappedModel(cfg.HaikuMappedModel),
+ }
+
+ if len(cfg.ExactModelMappings) > 0 {
+ out.ExactModelMappings = make(map[string]string, len(cfg.ExactModelMappings))
+ for requestedModel, mappedModel := range cfg.ExactModelMappings {
+ requestedModel = strings.TrimSpace(requestedModel)
+ mappedModel = normalizeOpenAIMessagesDispatchMappedModel(mappedModel)
+ if requestedModel == "" || mappedModel == "" {
+ continue
+ }
+ out.ExactModelMappings[requestedModel] = mappedModel
+ }
+ if len(out.ExactModelMappings) == 0 {
+ out.ExactModelMappings = nil
+ }
+ }
+
+ return out
+}
+
+func claudeMessagesDispatchFamily(model string) string {
+ normalized := strings.ToLower(strings.TrimSpace(model))
+ if !strings.HasPrefix(normalized, "claude") {
+ return ""
+ }
+ switch {
+ case strings.Contains(normalized, "opus"):
+ return "opus"
+ case strings.Contains(normalized, "sonnet"):
+ return "sonnet"
+ case strings.Contains(normalized, "haiku"):
+ return "haiku"
+ default:
+ return ""
+ }
+}
+
+func (g *Group) ResolveMessagesDispatchModel(requestedModel string) string {
+ if g == nil {
+ return ""
+ }
+ requestedModel = strings.TrimSpace(requestedModel)
+ if requestedModel == "" {
+ return ""
+ }
+
+ cfg := normalizeOpenAIMessagesDispatchModelConfig(g.MessagesDispatchModelConfig)
+ if mappedModel := strings.TrimSpace(cfg.ExactModelMappings[requestedModel]); mappedModel != "" {
+ return mappedModel
+ }
+
+ switch claudeMessagesDispatchFamily(requestedModel) {
+ case "opus":
+ if mappedModel := strings.TrimSpace(cfg.OpusMappedModel); mappedModel != "" {
+ return mappedModel
+ }
+ return defaultOpenAIMessagesDispatchOpusMappedModel
+ case "sonnet":
+ if mappedModel := strings.TrimSpace(cfg.SonnetMappedModel); mappedModel != "" {
+ return mappedModel
+ }
+ return defaultOpenAIMessagesDispatchSonnetMappedModel
+ case "haiku":
+ if mappedModel := strings.TrimSpace(cfg.HaikuMappedModel); mappedModel != "" {
+ return mappedModel
+ }
+ return defaultOpenAIMessagesDispatchHaikuMappedModel
+ default:
+ return ""
+ }
+}
+
+func sanitizeGroupMessagesDispatchFields(g *Group) {
+ if g == nil || g.Platform == PlatformOpenAI {
+ return
+ }
+ g.AllowMessagesDispatch = false
+ g.DefaultMappedModel = ""
+ g.MessagesDispatchModelConfig = OpenAIMessagesDispatchModelConfig{}
+}
diff --git a/backend/internal/service/openai_messages_dispatch_test.go b/backend/internal/service/openai_messages_dispatch_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..a625aaddd4359e7ca04bb817420061a63db7040f
--- /dev/null
+++ b/backend/internal/service/openai_messages_dispatch_test.go
@@ -0,0 +1,27 @@
+package service
+
+import "testing"
+
+import "github.com/stretchr/testify/require"
+
+func TestNormalizeOpenAIMessagesDispatchModelConfig(t *testing.T) {
+ t.Parallel()
+
+ cfg := normalizeOpenAIMessagesDispatchModelConfig(OpenAIMessagesDispatchModelConfig{
+ OpusMappedModel: " gpt-5.4-high ",
+ SonnetMappedModel: "gpt-5.3-codex",
+ HaikuMappedModel: " gpt-5.4-mini-medium ",
+ ExactModelMappings: map[string]string{
+ " claude-sonnet-4-5-20250929 ": " gpt-5.2-high ",
+ "": "gpt-5.4",
+ "claude-opus-4-6": " ",
+ },
+ })
+
+ require.Equal(t, "gpt-5.4", cfg.OpusMappedModel)
+ require.Equal(t, "gpt-5.3-codex", cfg.SonnetMappedModel)
+ require.Equal(t, "gpt-5.4-mini", cfg.HaikuMappedModel)
+ require.Equal(t, map[string]string{
+ "claude-sonnet-4-5-20250929": "gpt-5.2",
+ }, cfg.ExactModelMappings)
+}
diff --git a/backend/internal/service/openai_ws_ratelimit_signal_test.go b/backend/internal/service/openai_ws_ratelimit_signal_test.go
index ffe7915262483670a6fc6907a5d4d48c62081271..6313d0c08e1c742e17fde86d5a7fb8ce97e0b58e 100644
--- a/backend/internal/service/openai_ws_ratelimit_signal_test.go
+++ b/backend/internal/service/openai_ws_ratelimit_signal_test.go
@@ -492,7 +492,7 @@ func TestAdminService_ListAccounts_ExhaustedCodexExtraReturnsRateLimitedAccount(
}
svc := &adminServiceImpl{accountRepo: repo}
- accounts, total, err := svc.ListAccounts(context.Background(), 1, 20, PlatformOpenAI, AccountTypeOAuth, "", "", 0, "")
+ accounts, total, err := svc.ListAccounts(context.Background(), 1, 20, PlatformOpenAI, AccountTypeOAuth, "", "", 0, "", "", "")
require.NoError(t, err)
require.Equal(t, int64(1), total)
require.Len(t, accounts, 1)
diff --git a/backend/internal/service/ops_service.go b/backend/internal/service/ops_service.go
index 29f0aa8b5089bee575a0b0ab7c9b4fa13b3d1327..cd3974a00f06bfc65ff57e7f4a168fddec18983b 100644
--- a/backend/internal/service/ops_service.go
+++ b/backend/internal/service/ops_service.go
@@ -16,7 +16,7 @@ import (
var ErrOpsDisabled = infraerrors.NotFound("OPS_DISABLED", "Ops monitoring is disabled")
const (
- opsMaxStoredRequestBodyBytes = 10 * 1024
+ opsMaxStoredRequestBodyBytes = 256 * 1024
opsMaxStoredErrorBodyBytes = 20 * 1024
)
diff --git a/backend/internal/service/payment_config_limits.go b/backend/internal/service/payment_config_limits.go
new file mode 100644
index 0000000000000000000000000000000000000000..569052788564c04c21fa164299e99c20832edb7b
--- /dev/null
+++ b/backend/internal/service/payment_config_limits.go
@@ -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
+}
diff --git a/backend/internal/service/payment_config_limits_test.go b/backend/internal/service/payment_config_limits_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..73ad66ef03c60127040d391e7dfb091ebced8dc9
--- /dev/null
+++ b/backend/internal/service/payment_config_limits_test.go
@@ -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")
+ }
+ })
+}
diff --git a/backend/internal/service/payment_config_plans.go b/backend/internal/service/payment_config_plans.go
new file mode 100644
index 0000000000000000000000000000000000000000..8a3e2950cce26e344f05f87e046eb259de129b06
--- /dev/null
+++ b/backend/internal/service/payment_config_plans.go
@@ -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
+}
diff --git a/backend/internal/service/payment_config_providers.go b/backend/internal/service/payment_config_providers.go
new file mode 100644
index 0000000000000000000000000000000000000000..101819144233e3d612b5aaea0a4bdb07deae4136
--- /dev/null
+++ b/backend/internal/service/payment_config_providers.go
@@ -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
+}
diff --git a/backend/internal/service/payment_config_providers_test.go b/backend/internal/service/payment_config_providers_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..e71eb9f502b78db9d52a91ad5ccadba47707ddb6
--- /dev/null
+++ b/backend/internal/service/payment_config_providers_test.go
@@ -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)
+ })
+ }
+}
diff --git a/backend/internal/service/payment_config_service.go b/backend/internal/service/payment_config_service.go
new file mode 100644
index 0000000000000000000000000000000000000000..9042c3aba7a77542da89cc84925fffb36e500f62
--- /dev/null
+++ b/backend/internal/service/payment_config_service.go
@@ -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
+}
diff --git a/backend/internal/service/payment_config_service_test.go b/backend/internal/service/payment_config_service_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..027bb796fcde5f6dbae4d46c7dfc3ee7d2c1e99e
--- /dev/null
+++ b/backend/internal/service/payment_config_service_test.go
@@ -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)
+ }
+ })
+ }
+}
diff --git a/backend/internal/service/payment_fulfillment.go b/backend/internal/service/payment_fulfillment.go
new file mode 100644
index 0000000000000000000000000000000000000000..de41d7426164d389bdca5cfc31c9216d45fdebcc
--- /dev/null
+++ b/backend/internal/service/payment_fulfillment.go
@@ -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)
+}
diff --git a/backend/internal/service/payment_fulfillment_test.go b/backend/internal/service/payment_fulfillment_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..625b0d9f0ed9bed88489abc63bd7b5a5c24b6eb7
--- /dev/null
+++ b/backend/internal/service/payment_fulfillment_test.go
@@ -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))
+}
diff --git a/backend/internal/service/payment_order.go b/backend/internal/service/payment_order.go
new file mode 100644
index 0000000000000000000000000000000000000000..2a952ece100a7491031f8279d0980672ced743f5
--- /dev/null
+++ b/backend/internal/service/payment_order.go
@@ -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
+}
diff --git a/backend/internal/service/payment_order_expiry_service.go b/backend/internal/service/payment_order_expiry_service.go
new file mode 100644
index 0000000000000000000000000000000000000000..b0cda3e5914bc7fbf8c3ad73a70e420462a7264c
--- /dev/null
+++ b/backend/internal/service/payment_order_expiry_service.go
@@ -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)
+ }
+}
diff --git a/backend/internal/service/payment_order_lifecycle.go b/backend/internal/service/payment_order_lifecycle.go
new file mode 100644
index 0000000000000000000000000000000000000000..801471804c7597ea667ca86279b35177a3aec95f
--- /dev/null
+++ b/backend/internal/service/payment_order_lifecycle.go
@@ -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)
+}
diff --git a/backend/internal/service/payment_refund.go b/backend/internal/service/payment_refund.go
new file mode 100644
index 0000000000000000000000000000000000000000..fd2822cc41f86aedb80ae36e51d89e5bd38d31fc
--- /dev/null
+++ b/backend/internal/service/payment_refund.go
@@ -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)
+}
diff --git a/backend/internal/service/payment_service.go b/backend/internal/service/payment_service.go
new file mode 100644
index 0000000000000000000000000000000000000000..6d8b185e976fc89b02efd2782ed59d57fde59960
--- /dev/null
+++ b/backend/internal/service/payment_service.go
@@ -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
+}
diff --git a/backend/internal/service/payment_stats.go b/backend/internal/service/payment_stats.go
new file mode 100644
index 0000000000000000000000000000000000000000..d206b271717135e58b970a19fa032b4c0c4c00ba
--- /dev/null
+++ b/backend/internal/service/payment_stats.go
@@ -0,0 +1,163 @@
+package service
+
+import (
+ "context"
+ "encoding/json"
+ "log/slog"
+ "math"
+ "sort"
+ "strconv"
+ "time"
+
+ dbent "github.com/Wei-Shaw/sub2api/ent"
+ "github.com/Wei-Shaw/sub2api/ent/paymentauditlog"
+ "github.com/Wei-Shaw/sub2api/ent/paymentorder"
+)
+
+// --- Dashboard & Analytics ---
+
+func (s *PaymentService) GetDashboardStats(ctx context.Context, days int) (*DashboardStats, error) {
+ if days <= 0 {
+ days = 30
+ }
+ now := time.Now()
+ since := now.AddDate(0, 0, -days)
+ todayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
+
+ paidStatuses := []string{OrderStatusCompleted, OrderStatusPaid, OrderStatusRecharging}
+
+ orders, err := s.entClient.PaymentOrder.Query().
+ Where(
+ paymentorder.StatusIn(paidStatuses...),
+ paymentorder.PaidAtGTE(since),
+ ).
+ All(ctx)
+ if err != nil {
+ return nil, err
+ }
+
+ st := &DashboardStats{}
+ computeBasicStats(st, orders, todayStart)
+
+ st.PendingOrders, err = s.entClient.PaymentOrder.Query().
+ Where(paymentorder.StatusEQ(OrderStatusPending)).
+ Count(ctx)
+ if err != nil {
+ return nil, err
+ }
+
+ st.DailySeries = buildDailySeries(orders, since, days)
+ st.PaymentMethods = buildMethodDistribution(orders)
+ st.TopUsers = buildTopUsers(orders)
+
+ return st, nil
+}
+
+func computeBasicStats(st *DashboardStats, orders []*dbent.PaymentOrder, todayStart time.Time) {
+ var totalAmount, todayAmount float64
+ var todayCount int
+ for _, o := range orders {
+ totalAmount += o.PayAmount
+ if o.PaidAt != nil && !o.PaidAt.Before(todayStart) {
+ todayAmount += o.PayAmount
+ todayCount++
+ }
+ }
+ st.TotalAmount = math.Round(totalAmount*100) / 100
+ st.TodayAmount = math.Round(todayAmount*100) / 100
+ st.TotalCount = len(orders)
+ st.TodayCount = todayCount
+ if st.TotalCount > 0 {
+ st.AvgAmount = math.Round(totalAmount/float64(st.TotalCount)*100) / 100
+ }
+}
+
+func buildDailySeries(orders []*dbent.PaymentOrder, since time.Time, days int) []DailyStats {
+ dailyMap := make(map[string]*DailyStats)
+ for _, o := range orders {
+ if o.PaidAt == nil {
+ continue
+ }
+ date := o.PaidAt.Format("2006-01-02")
+ ds, ok := dailyMap[date]
+ if !ok {
+ ds = &DailyStats{Date: date}
+ dailyMap[date] = ds
+ }
+ ds.Amount += o.PayAmount
+ ds.Count++
+ }
+ series := make([]DailyStats, 0, days)
+ for i := 0; i < days; i++ {
+ date := since.AddDate(0, 0, i+1).Format("2006-01-02")
+ if ds, ok := dailyMap[date]; ok {
+ ds.Amount = math.Round(ds.Amount*100) / 100
+ series = append(series, *ds)
+ } else {
+ series = append(series, DailyStats{Date: date})
+ }
+ }
+ return series
+}
+
+func buildMethodDistribution(orders []*dbent.PaymentOrder) []PaymentMethodStat {
+ methodMap := make(map[string]*PaymentMethodStat)
+ for _, o := range orders {
+ ms, ok := methodMap[o.PaymentType]
+ if !ok {
+ ms = &PaymentMethodStat{Type: o.PaymentType}
+ methodMap[o.PaymentType] = ms
+ }
+ ms.Amount += o.PayAmount
+ ms.Count++
+ }
+ methods := make([]PaymentMethodStat, 0, len(methodMap))
+ for _, ms := range methodMap {
+ ms.Amount = math.Round(ms.Amount*100) / 100
+ methods = append(methods, *ms)
+ }
+ return methods
+}
+
+func buildTopUsers(orders []*dbent.PaymentOrder) []TopUserStat {
+ userMap := make(map[int64]*TopUserStat)
+ for _, o := range orders {
+ us, ok := userMap[o.UserID]
+ if !ok {
+ us = &TopUserStat{UserID: o.UserID, Email: o.UserEmail}
+ userMap[o.UserID] = us
+ }
+ us.Amount += o.PayAmount
+ }
+ userList := make([]*TopUserStat, 0, len(userMap))
+ for _, us := range userMap {
+ us.Amount = math.Round(us.Amount*100) / 100
+ userList = append(userList, us)
+ }
+ sort.Slice(userList, func(i, j int) bool {
+ return userList[i].Amount > userList[j].Amount
+ })
+ limit := topUsersLimit
+ if len(userList) < limit {
+ limit = len(userList)
+ }
+ result := make([]TopUserStat, 0, limit)
+ for i := 0; i < limit; i++ {
+ result = append(result, *userList[i])
+ }
+ return result
+}
+
+// --- Audit Logs ---
+
+func (s *PaymentService) writeAuditLog(ctx context.Context, oid int64, action, op string, detail map[string]any) {
+ dj, _ := json.Marshal(detail)
+ _, err := s.entClient.PaymentAuditLog.Create().SetOrderID(strconv.FormatInt(oid, 10)).SetAction(action).SetDetail(string(dj)).SetOperator(op).Save(ctx)
+ if err != nil {
+ slog.Error("audit log failed", "orderID", oid, "action", action, "error", err)
+ }
+}
+
+func (s *PaymentService) GetOrderAuditLogs(ctx context.Context, oid int64) ([]*dbent.PaymentAuditLog, error) {
+ return s.entClient.PaymentAuditLog.Query().Where(paymentauditlog.OrderIDEQ(strconv.FormatInt(oid, 10))).Order(paymentauditlog.ByCreatedAt()).All(ctx)
+}
diff --git a/backend/internal/service/ratelimit_service.go b/backend/internal/service/ratelimit_service.go
index 4f5b57cc978137646debe14cdca449e7647c9cef..4d8009b7bd39471da48550367d3eb18c656d4219 100644
--- a/backend/internal/service/ratelimit_service.go
+++ b/backend/internal/service/ratelimit_service.go
@@ -142,11 +142,16 @@ func (s *RateLimitService) HandleUpstreamError(ctx context.Context, account *Acc
switch statusCode {
case 400:
- // 只有当错误信息包含 "organization has been disabled" 时才禁用
+ // "organization has been disabled" → 永久禁用
if strings.Contains(strings.ToLower(upstreamMsg), "organization has been disabled") {
msg := "Organization disabled (400): " + upstreamMsg
s.handleAuthError(ctx, account, msg)
shouldDisable = true
+ } else if account.Platform == PlatformAnthropic && strings.Contains(strings.ToLower(upstreamMsg), "credit balance") {
+ // Anthropic API key 余额不足(语义等同 402),停止调度
+ msg := "Credit balance exhausted (400): " + upstreamMsg
+ s.handleAuthError(ctx, account, msg)
+ shouldDisable = true
}
// 其他 400 错误(如参数问题)不处理,不禁用账号
case 401:
diff --git a/backend/internal/service/scheduler_snapshot_hydration_test.go b/backend/internal/service/scheduler_snapshot_hydration_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..5c0b289b7a4d1714456555a585d6f4d7e6a82d8d
--- /dev/null
+++ b/backend/internal/service/scheduler_snapshot_hydration_test.go
@@ -0,0 +1,159 @@
+//go:build unit
+
+package service
+
+import (
+ "context"
+ "testing"
+ "time"
+)
+
+type snapshotHydrationCache struct {
+ snapshot []*Account
+ accounts map[int64]*Account
+}
+
+func (c *snapshotHydrationCache) GetSnapshot(ctx context.Context, bucket SchedulerBucket) ([]*Account, bool, error) {
+ return c.snapshot, true, nil
+}
+
+func (c *snapshotHydrationCache) SetSnapshot(ctx context.Context, bucket SchedulerBucket, accounts []Account) error {
+ return nil
+}
+
+func (c *snapshotHydrationCache) GetAccount(ctx context.Context, accountID int64) (*Account, error) {
+ if c.accounts == nil {
+ return nil, nil
+ }
+ return c.accounts[accountID], nil
+}
+
+func (c *snapshotHydrationCache) SetAccount(ctx context.Context, account *Account) error {
+ return nil
+}
+
+func (c *snapshotHydrationCache) DeleteAccount(ctx context.Context, accountID int64) error {
+ return nil
+}
+
+func (c *snapshotHydrationCache) UpdateLastUsed(ctx context.Context, updates map[int64]time.Time) error {
+ return nil
+}
+
+func (c *snapshotHydrationCache) TryLockBucket(ctx context.Context, bucket SchedulerBucket, ttl time.Duration) (bool, error) {
+ return true, nil
+}
+
+func (c *snapshotHydrationCache) ListBuckets(ctx context.Context) ([]SchedulerBucket, error) {
+ return nil, nil
+}
+
+func (c *snapshotHydrationCache) GetOutboxWatermark(ctx context.Context) (int64, error) {
+ return 0, nil
+}
+
+func (c *snapshotHydrationCache) SetOutboxWatermark(ctx context.Context, id int64) error {
+ return nil
+}
+
+func TestOpenAISelectAccountWithLoadAwareness_HydratesSelectedAccountFromSchedulerSnapshot(t *testing.T) {
+ cache := &snapshotHydrationCache{
+ snapshot: []*Account{
+ {
+ ID: 1,
+ Platform: PlatformOpenAI,
+ Type: AccountTypeAPIKey,
+ Status: StatusActive,
+ Schedulable: true,
+ Concurrency: 1,
+ Priority: 1,
+ Credentials: map[string]any{
+ "model_mapping": map[string]any{
+ "gpt-4": "gpt-4",
+ },
+ },
+ },
+ },
+ accounts: map[int64]*Account{
+ 1: {
+ ID: 1,
+ Platform: PlatformOpenAI,
+ Type: AccountTypeAPIKey,
+ Status: StatusActive,
+ Schedulable: true,
+ Concurrency: 1,
+ Priority: 1,
+ Credentials: map[string]any{
+ "api_key": "sk-live",
+ "model_mapping": map[string]any{"gpt-4": "gpt-4"},
+ },
+ },
+ },
+ }
+
+ schedulerSnapshot := NewSchedulerSnapshotService(cache, nil, nil, nil, nil)
+ groupID := int64(2)
+ svc := &OpenAIGatewayService{
+ schedulerSnapshot: schedulerSnapshot,
+ cache: &stubGatewayCache{},
+ }
+
+ selection, err := svc.SelectAccountWithLoadAwareness(context.Background(), &groupID, "", "gpt-4", nil)
+ if err != nil {
+ t.Fatalf("SelectAccountWithLoadAwareness error: %v", err)
+ }
+ if selection == nil || selection.Account == nil {
+ t.Fatalf("expected selected account")
+ }
+ if got := selection.Account.GetOpenAIApiKey(); got != "sk-live" {
+ t.Fatalf("expected hydrated api key, got %q", got)
+ }
+}
+
+func TestGatewaySelectAccountWithLoadAwareness_HydratesSelectedAccountFromSchedulerSnapshot(t *testing.T) {
+ cache := &snapshotHydrationCache{
+ snapshot: []*Account{
+ {
+ ID: 9,
+ Platform: PlatformAnthropic,
+ Type: AccountTypeAPIKey,
+ Status: StatusActive,
+ Schedulable: true,
+ Concurrency: 1,
+ Priority: 1,
+ },
+ },
+ accounts: map[int64]*Account{
+ 9: {
+ ID: 9,
+ Platform: PlatformAnthropic,
+ Type: AccountTypeAPIKey,
+ Status: StatusActive,
+ Schedulable: true,
+ Concurrency: 1,
+ Priority: 1,
+ Credentials: map[string]any{
+ "api_key": "anthropic-live-key",
+ },
+ },
+ },
+ }
+
+ schedulerSnapshot := NewSchedulerSnapshotService(cache, nil, nil, nil, nil)
+ svc := &GatewayService{
+ schedulerSnapshot: schedulerSnapshot,
+ cache: &mockGatewayCacheForPlatform{},
+ cfg: testConfig(),
+ }
+
+ result, err := svc.SelectAccountWithLoadAwareness(context.Background(), nil, "", "claude-3-5-sonnet-20241022", nil, "", 0)
+ if err != nil {
+ t.Fatalf("SelectAccountWithLoadAwareness error: %v", err)
+ }
+ if result == nil || result.Account == nil {
+ t.Fatalf("expected selected account")
+ }
+ if got := result.Account.GetCredential("api_key"); got != "anthropic-live-key" {
+ t.Fatalf("expected hydrated api key, got %q", got)
+ }
+}
diff --git a/backend/internal/service/setting_service.go b/backend/internal/service/setting_service.go
index 60a697227860625b5a75899dcf5bdf679d11f76e..f00ff640394fd5aacebe3ee44fec98b8238700ab 100644
--- a/backend/internal/service/setting_service.go
+++ b/backend/internal/service/setting_service.go
@@ -9,6 +9,7 @@ import (
"fmt"
"log/slog"
"net/url"
+ "sort"
"strconv"
"strings"
"sync/atomic"
@@ -16,6 +17,7 @@ import (
"github.com/Wei-Shaw/sub2api/internal/config"
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
+ "github.com/imroc/req/v3"
"golang.org/x/sync/singleflight"
)
@@ -160,10 +162,15 @@ func (s *SettingService) GetPublicSettings(ctx context.Context) (*PublicSettings
SettingKeyHideCcsImportButton,
SettingKeyPurchaseSubscriptionEnabled,
SettingKeyPurchaseSubscriptionURL,
+ SettingKeyTableDefaultPageSize,
+ SettingKeyTablePageSizeOptions,
SettingKeyCustomMenuItems,
SettingKeyCustomEndpoints,
SettingKeyLinuxDoConnectEnabled,
SettingKeyBackendModeEnabled,
+ SettingKeyOIDCConnectEnabled,
+ SettingKeyOIDCConnectProviderName,
+ SettingPaymentEnabled,
}
settings, err := s.settingRepo.GetMultiple(ctx, keys)
@@ -177,6 +184,19 @@ func (s *SettingService) GetPublicSettings(ctx context.Context) (*PublicSettings
} else {
linuxDoEnabled = s.cfg != nil && s.cfg.LinuxDo.Enabled
}
+ oidcEnabled := false
+ if raw, ok := settings[SettingKeyOIDCConnectEnabled]; ok {
+ oidcEnabled = raw == "true"
+ } else {
+ oidcEnabled = s.cfg != nil && s.cfg.OIDC.Enabled
+ }
+ oidcProviderName := strings.TrimSpace(settings[SettingKeyOIDCConnectProviderName])
+ if oidcProviderName == "" && s.cfg != nil {
+ oidcProviderName = strings.TrimSpace(s.cfg.OIDC.ProviderName)
+ }
+ if oidcProviderName == "" {
+ oidcProviderName = "OIDC"
+ }
// Password reset requires email verification to be enabled
emailVerifyEnabled := settings[SettingKeyEmailVerifyEnabled] == "true"
@@ -184,6 +204,10 @@ func (s *SettingService) GetPublicSettings(ctx context.Context) (*PublicSettings
registrationEmailSuffixWhitelist := ParseRegistrationEmailSuffixWhitelist(
settings[SettingKeyRegistrationEmailSuffixWhitelist],
)
+ tableDefaultPageSize, tablePageSizeOptions := parseTablePreferences(
+ settings[SettingKeyTableDefaultPageSize],
+ settings[SettingKeyTablePageSizeOptions],
+ )
return &PublicSettings{
RegistrationEnabled: settings[SettingKeyRegistrationEnabled] == "true",
@@ -205,10 +229,15 @@ func (s *SettingService) GetPublicSettings(ctx context.Context) (*PublicSettings
HideCcsImportButton: settings[SettingKeyHideCcsImportButton] == "true",
PurchaseSubscriptionEnabled: settings[SettingKeyPurchaseSubscriptionEnabled] == "true",
PurchaseSubscriptionURL: strings.TrimSpace(settings[SettingKeyPurchaseSubscriptionURL]),
+ TableDefaultPageSize: tableDefaultPageSize,
+ TablePageSizeOptions: tablePageSizeOptions,
CustomMenuItems: settings[SettingKeyCustomMenuItems],
CustomEndpoints: settings[SettingKeyCustomEndpoints],
LinuxDoOAuthEnabled: linuxDoEnabled,
BackendModeEnabled: settings[SettingKeyBackendModeEnabled] == "true",
+ OIDCOAuthEnabled: oidcEnabled,
+ OIDCOAuthProviderName: oidcProviderName,
+ PaymentEnabled: settings[SettingPaymentEnabled] == "true",
}, nil
}
@@ -252,10 +281,15 @@ func (s *SettingService) GetPublicSettingsForInjection(ctx context.Context) (any
HideCcsImportButton bool `json:"hide_ccs_import_button"`
PurchaseSubscriptionEnabled bool `json:"purchase_subscription_enabled"`
PurchaseSubscriptionURL string `json:"purchase_subscription_url,omitempty"`
+ TableDefaultPageSize int `json:"table_default_page_size"`
+ TablePageSizeOptions []int `json:"table_page_size_options"`
CustomMenuItems json.RawMessage `json:"custom_menu_items"`
CustomEndpoints json.RawMessage `json:"custom_endpoints"`
LinuxDoOAuthEnabled bool `json:"linuxdo_oauth_enabled"`
BackendModeEnabled bool `json:"backend_mode_enabled"`
+ OIDCOAuthEnabled bool `json:"oidc_oauth_enabled"`
+ OIDCOAuthProviderName string `json:"oidc_oauth_provider_name"`
+ PaymentEnabled bool `json:"payment_enabled"`
Version string `json:"version,omitempty"`
}{
RegistrationEnabled: settings.RegistrationEnabled,
@@ -277,10 +311,15 @@ func (s *SettingService) GetPublicSettingsForInjection(ctx context.Context) (any
HideCcsImportButton: settings.HideCcsImportButton,
PurchaseSubscriptionEnabled: settings.PurchaseSubscriptionEnabled,
PurchaseSubscriptionURL: settings.PurchaseSubscriptionURL,
+ TableDefaultPageSize: settings.TableDefaultPageSize,
+ TablePageSizeOptions: settings.TablePageSizeOptions,
CustomMenuItems: filterUserVisibleMenuItems(settings.CustomMenuItems),
CustomEndpoints: safeRawJSONArray(settings.CustomEndpoints),
LinuxDoOAuthEnabled: settings.LinuxDoOAuthEnabled,
BackendModeEnabled: settings.BackendModeEnabled,
+ OIDCOAuthEnabled: settings.OIDCOAuthEnabled,
+ OIDCOAuthProviderName: settings.OIDCOAuthProviderName,
+ PaymentEnabled: settings.PaymentEnabled,
Version: s.version,
}, nil
}
@@ -333,8 +372,8 @@ func safeRawJSONArray(raw string) json.RawMessage {
return json.RawMessage("[]")
}
-// GetFrameSrcOrigins returns deduplicated http(s) origins from purchase_subscription_url
-// and all custom_menu_items URLs. Used by the router layer for CSP frame-src injection.
+// GetFrameSrcOrigins returns deduplicated http(s) origins from home_content URL,
+// purchase_subscription_url, and all custom_menu_items URLs. Used by the router layer for CSP frame-src injection.
func (s *SettingService) GetFrameSrcOrigins(ctx context.Context) ([]string, error) {
settings, err := s.GetPublicSettings(ctx)
if err != nil {
@@ -353,6 +392,9 @@ func (s *SettingService) GetFrameSrcOrigins(ctx context.Context) ([]string, erro
}
}
+ // home content URL (when home_content is set to a URL for iframe embedding)
+ addOrigin(settings.HomeContent)
+
// purchase subscription URL
if settings.PurchaseSubscriptionEnabled {
addOrigin(settings.PurchaseSubscriptionURL)
@@ -460,6 +502,32 @@ func (s *SettingService) UpdateSettings(ctx context.Context, settings *SystemSet
updates[SettingKeyLinuxDoConnectClientSecret] = settings.LinuxDoConnectClientSecret
}
+ // Generic OIDC OAuth 登录
+ updates[SettingKeyOIDCConnectEnabled] = strconv.FormatBool(settings.OIDCConnectEnabled)
+ updates[SettingKeyOIDCConnectProviderName] = settings.OIDCConnectProviderName
+ updates[SettingKeyOIDCConnectClientID] = settings.OIDCConnectClientID
+ updates[SettingKeyOIDCConnectIssuerURL] = settings.OIDCConnectIssuerURL
+ updates[SettingKeyOIDCConnectDiscoveryURL] = settings.OIDCConnectDiscoveryURL
+ updates[SettingKeyOIDCConnectAuthorizeURL] = settings.OIDCConnectAuthorizeURL
+ updates[SettingKeyOIDCConnectTokenURL] = settings.OIDCConnectTokenURL
+ updates[SettingKeyOIDCConnectUserInfoURL] = settings.OIDCConnectUserInfoURL
+ updates[SettingKeyOIDCConnectJWKSURL] = settings.OIDCConnectJWKSURL
+ updates[SettingKeyOIDCConnectScopes] = settings.OIDCConnectScopes
+ updates[SettingKeyOIDCConnectRedirectURL] = settings.OIDCConnectRedirectURL
+ updates[SettingKeyOIDCConnectFrontendRedirectURL] = settings.OIDCConnectFrontendRedirectURL
+ updates[SettingKeyOIDCConnectTokenAuthMethod] = settings.OIDCConnectTokenAuthMethod
+ updates[SettingKeyOIDCConnectUsePKCE] = strconv.FormatBool(settings.OIDCConnectUsePKCE)
+ updates[SettingKeyOIDCConnectValidateIDToken] = strconv.FormatBool(settings.OIDCConnectValidateIDToken)
+ updates[SettingKeyOIDCConnectAllowedSigningAlgs] = settings.OIDCConnectAllowedSigningAlgs
+ updates[SettingKeyOIDCConnectClockSkewSeconds] = strconv.Itoa(settings.OIDCConnectClockSkewSeconds)
+ updates[SettingKeyOIDCConnectRequireEmailVerified] = strconv.FormatBool(settings.OIDCConnectRequireEmailVerified)
+ updates[SettingKeyOIDCConnectUserInfoEmailPath] = settings.OIDCConnectUserInfoEmailPath
+ updates[SettingKeyOIDCConnectUserInfoIDPath] = settings.OIDCConnectUserInfoIDPath
+ updates[SettingKeyOIDCConnectUserInfoUsernamePath] = settings.OIDCConnectUserInfoUsernamePath
+ if settings.OIDCConnectClientSecret != "" {
+ updates[SettingKeyOIDCConnectClientSecret] = settings.OIDCConnectClientSecret
+ }
+
// OEM设置
updates[SettingKeySiteName] = settings.SiteName
updates[SettingKeySiteLogo] = settings.SiteLogo
@@ -471,6 +539,16 @@ func (s *SettingService) UpdateSettings(ctx context.Context, settings *SystemSet
updates[SettingKeyHideCcsImportButton] = strconv.FormatBool(settings.HideCcsImportButton)
updates[SettingKeyPurchaseSubscriptionEnabled] = strconv.FormatBool(settings.PurchaseSubscriptionEnabled)
updates[SettingKeyPurchaseSubscriptionURL] = strings.TrimSpace(settings.PurchaseSubscriptionURL)
+ tableDefaultPageSize, tablePageSizeOptions := normalizeTablePreferences(
+ settings.TableDefaultPageSize,
+ settings.TablePageSizeOptions,
+ )
+ updates[SettingKeyTableDefaultPageSize] = strconv.Itoa(tableDefaultPageSize)
+ tablePageSizeOptionsJSON, err := json.Marshal(tablePageSizeOptions)
+ if err != nil {
+ return fmt.Errorf("marshal table page size options: %w", err)
+ }
+ updates[SettingKeyTablePageSizeOptions] = string(tablePageSizeOptionsJSON)
updates[SettingKeyCustomMenuItems] = settings.CustomMenuItems
updates[SettingKeyCustomEndpoints] = settings.CustomEndpoints
@@ -824,8 +902,12 @@ func (s *SettingService) InitializeDefaultSettings(ctx context.Context) error {
SettingKeySiteLogo: "",
SettingKeyPurchaseSubscriptionEnabled: "false",
SettingKeyPurchaseSubscriptionURL: "",
+ SettingKeyTableDefaultPageSize: "20",
+ SettingKeyTablePageSizeOptions: "[10,20,50,100]",
SettingKeyCustomMenuItems: "[]",
SettingKeyCustomEndpoints: "[]",
+ SettingKeyOIDCConnectEnabled: "false",
+ SettingKeyOIDCConnectProviderName: "OIDC",
SettingKeyDefaultConcurrency: strconv.Itoa(s.cfg.Default.UserConcurrency),
SettingKeyDefaultBalance: strconv.FormatFloat(s.cfg.Default.UserBalance, 'f', 8, 64),
SettingKeyDefaultSubscriptions: "[]",
@@ -893,6 +975,10 @@ func (s *SettingService) parseSettings(settings map[string]string) *SystemSettin
CustomEndpoints: settings[SettingKeyCustomEndpoints],
BackendModeEnabled: settings[SettingKeyBackendModeEnabled] == "true",
}
+ result.TableDefaultPageSize, result.TablePageSizeOptions = parseTablePreferences(
+ settings[SettingKeyTableDefaultPageSize],
+ settings[SettingKeyTablePageSizeOptions],
+ )
// 解析整数类型
if port, err := strconv.Atoi(settings[SettingKeySMTPPort]); err == nil {
@@ -951,6 +1037,138 @@ func (s *SettingService) parseSettings(settings map[string]string) *SystemSettin
}
result.LinuxDoConnectClientSecretConfigured = result.LinuxDoConnectClientSecret != ""
+ // Generic OIDC 设置:
+ // - 兼容 config.yaml/env
+ // - 支持后台系统设置覆盖并持久化(存储于 DB)
+ oidcBase := config.OIDCConnectConfig{}
+ if s.cfg != nil {
+ oidcBase = s.cfg.OIDC
+ }
+
+ if raw, ok := settings[SettingKeyOIDCConnectEnabled]; ok {
+ result.OIDCConnectEnabled = raw == "true"
+ } else {
+ result.OIDCConnectEnabled = oidcBase.Enabled
+ }
+
+ if v, ok := settings[SettingKeyOIDCConnectProviderName]; ok && strings.TrimSpace(v) != "" {
+ result.OIDCConnectProviderName = strings.TrimSpace(v)
+ } else {
+ result.OIDCConnectProviderName = strings.TrimSpace(oidcBase.ProviderName)
+ }
+ if result.OIDCConnectProviderName == "" {
+ result.OIDCConnectProviderName = "OIDC"
+ }
+
+ if v, ok := settings[SettingKeyOIDCConnectClientID]; ok && strings.TrimSpace(v) != "" {
+ result.OIDCConnectClientID = strings.TrimSpace(v)
+ } else {
+ result.OIDCConnectClientID = strings.TrimSpace(oidcBase.ClientID)
+ }
+ if v, ok := settings[SettingKeyOIDCConnectIssuerURL]; ok && strings.TrimSpace(v) != "" {
+ result.OIDCConnectIssuerURL = strings.TrimSpace(v)
+ } else {
+ result.OIDCConnectIssuerURL = strings.TrimSpace(oidcBase.IssuerURL)
+ }
+ if v, ok := settings[SettingKeyOIDCConnectDiscoveryURL]; ok && strings.TrimSpace(v) != "" {
+ result.OIDCConnectDiscoveryURL = strings.TrimSpace(v)
+ } else {
+ result.OIDCConnectDiscoveryURL = strings.TrimSpace(oidcBase.DiscoveryURL)
+ }
+ if v, ok := settings[SettingKeyOIDCConnectAuthorizeURL]; ok && strings.TrimSpace(v) != "" {
+ result.OIDCConnectAuthorizeURL = strings.TrimSpace(v)
+ } else {
+ result.OIDCConnectAuthorizeURL = strings.TrimSpace(oidcBase.AuthorizeURL)
+ }
+ if v, ok := settings[SettingKeyOIDCConnectTokenURL]; ok && strings.TrimSpace(v) != "" {
+ result.OIDCConnectTokenURL = strings.TrimSpace(v)
+ } else {
+ result.OIDCConnectTokenURL = strings.TrimSpace(oidcBase.TokenURL)
+ }
+ if v, ok := settings[SettingKeyOIDCConnectUserInfoURL]; ok && strings.TrimSpace(v) != "" {
+ result.OIDCConnectUserInfoURL = strings.TrimSpace(v)
+ } else {
+ result.OIDCConnectUserInfoURL = strings.TrimSpace(oidcBase.UserInfoURL)
+ }
+ if v, ok := settings[SettingKeyOIDCConnectJWKSURL]; ok && strings.TrimSpace(v) != "" {
+ result.OIDCConnectJWKSURL = strings.TrimSpace(v)
+ } else {
+ result.OIDCConnectJWKSURL = strings.TrimSpace(oidcBase.JWKSURL)
+ }
+ if v, ok := settings[SettingKeyOIDCConnectScopes]; ok && strings.TrimSpace(v) != "" {
+ result.OIDCConnectScopes = strings.TrimSpace(v)
+ } else {
+ result.OIDCConnectScopes = strings.TrimSpace(oidcBase.Scopes)
+ }
+ if v, ok := settings[SettingKeyOIDCConnectRedirectURL]; ok && strings.TrimSpace(v) != "" {
+ result.OIDCConnectRedirectURL = strings.TrimSpace(v)
+ } else {
+ result.OIDCConnectRedirectURL = strings.TrimSpace(oidcBase.RedirectURL)
+ }
+ if v, ok := settings[SettingKeyOIDCConnectFrontendRedirectURL]; ok && strings.TrimSpace(v) != "" {
+ result.OIDCConnectFrontendRedirectURL = strings.TrimSpace(v)
+ } else {
+ result.OIDCConnectFrontendRedirectURL = strings.TrimSpace(oidcBase.FrontendRedirectURL)
+ }
+ if v, ok := settings[SettingKeyOIDCConnectTokenAuthMethod]; ok && strings.TrimSpace(v) != "" {
+ result.OIDCConnectTokenAuthMethod = strings.ToLower(strings.TrimSpace(v))
+ } else {
+ result.OIDCConnectTokenAuthMethod = strings.ToLower(strings.TrimSpace(oidcBase.TokenAuthMethod))
+ }
+ if raw, ok := settings[SettingKeyOIDCConnectUsePKCE]; ok {
+ result.OIDCConnectUsePKCE = raw == "true"
+ } else {
+ result.OIDCConnectUsePKCE = oidcBase.UsePKCE
+ }
+ if raw, ok := settings[SettingKeyOIDCConnectValidateIDToken]; ok {
+ result.OIDCConnectValidateIDToken = raw == "true"
+ } else {
+ result.OIDCConnectValidateIDToken = oidcBase.ValidateIDToken
+ }
+ if v, ok := settings[SettingKeyOIDCConnectAllowedSigningAlgs]; ok && strings.TrimSpace(v) != "" {
+ result.OIDCConnectAllowedSigningAlgs = strings.TrimSpace(v)
+ } else {
+ result.OIDCConnectAllowedSigningAlgs = strings.TrimSpace(oidcBase.AllowedSigningAlgs)
+ }
+ clockSkewSet := false
+ if raw, ok := settings[SettingKeyOIDCConnectClockSkewSeconds]; ok && strings.TrimSpace(raw) != "" {
+ if parsed, err := strconv.Atoi(strings.TrimSpace(raw)); err == nil {
+ result.OIDCConnectClockSkewSeconds = parsed
+ clockSkewSet = true
+ }
+ }
+ if !clockSkewSet {
+ result.OIDCConnectClockSkewSeconds = oidcBase.ClockSkewSeconds
+ }
+ if !clockSkewSet && result.OIDCConnectClockSkewSeconds == 0 {
+ result.OIDCConnectClockSkewSeconds = 120
+ }
+ if raw, ok := settings[SettingKeyOIDCConnectRequireEmailVerified]; ok {
+ result.OIDCConnectRequireEmailVerified = raw == "true"
+ } else {
+ result.OIDCConnectRequireEmailVerified = oidcBase.RequireEmailVerified
+ }
+ if v, ok := settings[SettingKeyOIDCConnectUserInfoEmailPath]; ok {
+ result.OIDCConnectUserInfoEmailPath = strings.TrimSpace(v)
+ } else {
+ result.OIDCConnectUserInfoEmailPath = strings.TrimSpace(oidcBase.UserInfoEmailPath)
+ }
+ if v, ok := settings[SettingKeyOIDCConnectUserInfoIDPath]; ok {
+ result.OIDCConnectUserInfoIDPath = strings.TrimSpace(v)
+ } else {
+ result.OIDCConnectUserInfoIDPath = strings.TrimSpace(oidcBase.UserInfoIDPath)
+ }
+ if v, ok := settings[SettingKeyOIDCConnectUserInfoUsernamePath]; ok {
+ result.OIDCConnectUserInfoUsernamePath = strings.TrimSpace(v)
+ } else {
+ result.OIDCConnectUserInfoUsernamePath = strings.TrimSpace(oidcBase.UserInfoUsernamePath)
+ }
+ result.OIDCConnectClientSecret = strings.TrimSpace(settings[SettingKeyOIDCConnectClientSecret])
+ if result.OIDCConnectClientSecret == "" {
+ result.OIDCConnectClientSecret = strings.TrimSpace(oidcBase.ClientSecret)
+ }
+ result.OIDCConnectClientSecretConfigured = result.OIDCConnectClientSecret != ""
+
// Model fallback settings
result.EnableModelFallback = settings[SettingKeyEnableModelFallback] == "true"
result.FallbackModelAnthropic = s.getStringOrDefault(settings, SettingKeyFallbackModelAnthropic, "claude-3-5-sonnet-20241022")
@@ -1036,6 +1254,50 @@ func parseDefaultSubscriptions(raw string) []DefaultSubscriptionSetting {
return normalized
}
+func parseTablePreferences(defaultPageSizeRaw, optionsRaw string) (int, []int) {
+ defaultPageSize := 20
+ if v, err := strconv.Atoi(strings.TrimSpace(defaultPageSizeRaw)); err == nil {
+ defaultPageSize = v
+ }
+
+ var options []int
+ if strings.TrimSpace(optionsRaw) != "" {
+ _ = json.Unmarshal([]byte(optionsRaw), &options)
+ }
+
+ return normalizeTablePreferences(defaultPageSize, options)
+}
+
+func normalizeTablePreferences(defaultPageSize int, options []int) (int, []int) {
+ const minPageSize = 5
+ const maxPageSize = 1000
+ const fallbackPageSize = 20
+
+ seen := make(map[int]struct{}, len(options))
+ normalizedOptions := make([]int, 0, len(options))
+ for _, option := range options {
+ if option < minPageSize || option > maxPageSize {
+ continue
+ }
+ if _, ok := seen[option]; ok {
+ continue
+ }
+ seen[option] = struct{}{}
+ normalizedOptions = append(normalizedOptions, option)
+ }
+ sort.Ints(normalizedOptions)
+
+ if defaultPageSize < minPageSize || defaultPageSize > maxPageSize {
+ defaultPageSize = fallbackPageSize
+ }
+
+ if len(normalizedOptions) == 0 {
+ normalizedOptions = []int{10, 20, 50}
+ }
+
+ return defaultPageSize, normalizedOptions
+}
+
// getStringOrDefault 获取字符串值或默认值
func (s *SettingService) getStringOrDefault(settings map[string]string, key, defaultValue string) string {
if value, ok := settings[key]; ok && value != "" {
@@ -1323,6 +1585,282 @@ func (s *SettingService) SetOverloadCooldownSettings(ctx context.Context, settin
return s.settingRepo.Set(ctx, SettingKeyOverloadCooldownSettings, string(data))
}
+// GetOIDCConnectOAuthConfig 返回用于登录的“最终生效” OIDC 配置。
+//
+// 优先级:
+// - 若对应系统设置键存在,则覆盖 config.yaml/env 的值
+// - 否则回退到 config.yaml/env 的值
+func (s *SettingService) GetOIDCConnectOAuthConfig(ctx context.Context) (config.OIDCConnectConfig, error) {
+ if s == nil || s.cfg == nil {
+ return config.OIDCConnectConfig{}, infraerrors.ServiceUnavailable("CONFIG_NOT_READY", "config not loaded")
+ }
+
+ effective := s.cfg.OIDC
+
+ keys := []string{
+ SettingKeyOIDCConnectEnabled,
+ SettingKeyOIDCConnectProviderName,
+ SettingKeyOIDCConnectClientID,
+ SettingKeyOIDCConnectClientSecret,
+ SettingKeyOIDCConnectIssuerURL,
+ SettingKeyOIDCConnectDiscoveryURL,
+ SettingKeyOIDCConnectAuthorizeURL,
+ SettingKeyOIDCConnectTokenURL,
+ SettingKeyOIDCConnectUserInfoURL,
+ SettingKeyOIDCConnectJWKSURL,
+ SettingKeyOIDCConnectScopes,
+ SettingKeyOIDCConnectRedirectURL,
+ SettingKeyOIDCConnectFrontendRedirectURL,
+ SettingKeyOIDCConnectTokenAuthMethod,
+ SettingKeyOIDCConnectUsePKCE,
+ SettingKeyOIDCConnectValidateIDToken,
+ SettingKeyOIDCConnectAllowedSigningAlgs,
+ SettingKeyOIDCConnectClockSkewSeconds,
+ SettingKeyOIDCConnectRequireEmailVerified,
+ SettingKeyOIDCConnectUserInfoEmailPath,
+ SettingKeyOIDCConnectUserInfoIDPath,
+ SettingKeyOIDCConnectUserInfoUsernamePath,
+ }
+ settings, err := s.settingRepo.GetMultiple(ctx, keys)
+ if err != nil {
+ return config.OIDCConnectConfig{}, fmt.Errorf("get oidc connect settings: %w", err)
+ }
+
+ if raw, ok := settings[SettingKeyOIDCConnectEnabled]; ok {
+ effective.Enabled = raw == "true"
+ }
+ if v, ok := settings[SettingKeyOIDCConnectProviderName]; ok && strings.TrimSpace(v) != "" {
+ effective.ProviderName = strings.TrimSpace(v)
+ }
+ if v, ok := settings[SettingKeyOIDCConnectClientID]; ok && strings.TrimSpace(v) != "" {
+ effective.ClientID = strings.TrimSpace(v)
+ }
+ if v, ok := settings[SettingKeyOIDCConnectClientSecret]; ok && strings.TrimSpace(v) != "" {
+ effective.ClientSecret = strings.TrimSpace(v)
+ }
+ if v, ok := settings[SettingKeyOIDCConnectIssuerURL]; ok && strings.TrimSpace(v) != "" {
+ effective.IssuerURL = strings.TrimSpace(v)
+ }
+ if v, ok := settings[SettingKeyOIDCConnectDiscoveryURL]; ok && strings.TrimSpace(v) != "" {
+ effective.DiscoveryURL = strings.TrimSpace(v)
+ }
+ if v, ok := settings[SettingKeyOIDCConnectAuthorizeURL]; ok && strings.TrimSpace(v) != "" {
+ effective.AuthorizeURL = strings.TrimSpace(v)
+ }
+ if v, ok := settings[SettingKeyOIDCConnectTokenURL]; ok && strings.TrimSpace(v) != "" {
+ effective.TokenURL = strings.TrimSpace(v)
+ }
+ if v, ok := settings[SettingKeyOIDCConnectUserInfoURL]; ok && strings.TrimSpace(v) != "" {
+ effective.UserInfoURL = strings.TrimSpace(v)
+ }
+ if v, ok := settings[SettingKeyOIDCConnectJWKSURL]; ok && strings.TrimSpace(v) != "" {
+ effective.JWKSURL = strings.TrimSpace(v)
+ }
+ if v, ok := settings[SettingKeyOIDCConnectScopes]; ok && strings.TrimSpace(v) != "" {
+ effective.Scopes = strings.TrimSpace(v)
+ }
+ if v, ok := settings[SettingKeyOIDCConnectRedirectURL]; ok && strings.TrimSpace(v) != "" {
+ effective.RedirectURL = strings.TrimSpace(v)
+ }
+ if v, ok := settings[SettingKeyOIDCConnectFrontendRedirectURL]; ok && strings.TrimSpace(v) != "" {
+ effective.FrontendRedirectURL = strings.TrimSpace(v)
+ }
+ if v, ok := settings[SettingKeyOIDCConnectTokenAuthMethod]; ok && strings.TrimSpace(v) != "" {
+ effective.TokenAuthMethod = strings.ToLower(strings.TrimSpace(v))
+ }
+ if raw, ok := settings[SettingKeyOIDCConnectUsePKCE]; ok {
+ effective.UsePKCE = raw == "true"
+ }
+ if raw, ok := settings[SettingKeyOIDCConnectValidateIDToken]; ok {
+ effective.ValidateIDToken = raw == "true"
+ }
+ if v, ok := settings[SettingKeyOIDCConnectAllowedSigningAlgs]; ok && strings.TrimSpace(v) != "" {
+ effective.AllowedSigningAlgs = strings.TrimSpace(v)
+ }
+ if raw, ok := settings[SettingKeyOIDCConnectClockSkewSeconds]; ok && strings.TrimSpace(raw) != "" {
+ if parsed, parseErr := strconv.Atoi(strings.TrimSpace(raw)); parseErr == nil {
+ effective.ClockSkewSeconds = parsed
+ }
+ }
+ if raw, ok := settings[SettingKeyOIDCConnectRequireEmailVerified]; ok {
+ effective.RequireEmailVerified = raw == "true"
+ }
+ if v, ok := settings[SettingKeyOIDCConnectUserInfoEmailPath]; ok {
+ effective.UserInfoEmailPath = strings.TrimSpace(v)
+ }
+ if v, ok := settings[SettingKeyOIDCConnectUserInfoIDPath]; ok {
+ effective.UserInfoIDPath = strings.TrimSpace(v)
+ }
+ if v, ok := settings[SettingKeyOIDCConnectUserInfoUsernamePath]; ok {
+ effective.UserInfoUsernamePath = strings.TrimSpace(v)
+ }
+
+ if !effective.Enabled {
+ return config.OIDCConnectConfig{}, infraerrors.NotFound("OAUTH_DISABLED", "oauth login is disabled")
+ }
+ if strings.TrimSpace(effective.ProviderName) == "" {
+ effective.ProviderName = "OIDC"
+ }
+ if strings.TrimSpace(effective.ClientID) == "" {
+ return config.OIDCConnectConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "oauth client id not configured")
+ }
+ if strings.TrimSpace(effective.IssuerURL) == "" {
+ return config.OIDCConnectConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "oauth issuer url not configured")
+ }
+ if strings.TrimSpace(effective.RedirectURL) == "" {
+ return config.OIDCConnectConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "oauth redirect url not configured")
+ }
+ if strings.TrimSpace(effective.FrontendRedirectURL) == "" {
+ return config.OIDCConnectConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "oauth frontend redirect url not configured")
+ }
+ if !scopesContainOpenID(effective.Scopes) {
+ return config.OIDCConnectConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "oauth scopes must contain openid")
+ }
+ if effective.ClockSkewSeconds < 0 || effective.ClockSkewSeconds > 600 {
+ return config.OIDCConnectConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "oauth clock skew must be between 0 and 600")
+ }
+
+ if err := config.ValidateAbsoluteHTTPURL(effective.IssuerURL); err != nil {
+ return config.OIDCConnectConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "oauth issuer url invalid")
+ }
+
+ discoveryURL := strings.TrimSpace(effective.DiscoveryURL)
+ if discoveryURL == "" {
+ discoveryURL = oidcDefaultDiscoveryURL(effective.IssuerURL)
+ effective.DiscoveryURL = discoveryURL
+ }
+ if discoveryURL != "" {
+ if err := config.ValidateAbsoluteHTTPURL(discoveryURL); err != nil {
+ return config.OIDCConnectConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "oauth discovery url invalid")
+ }
+ }
+
+ needsDiscovery := strings.TrimSpace(effective.AuthorizeURL) == "" ||
+ strings.TrimSpace(effective.TokenURL) == "" ||
+ (effective.ValidateIDToken && strings.TrimSpace(effective.JWKSURL) == "")
+ if needsDiscovery && discoveryURL != "" {
+ metadata, resolveErr := oidcResolveProviderMetadata(ctx, discoveryURL)
+ if resolveErr != nil {
+ return config.OIDCConnectConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "oauth discovery resolve failed").WithCause(resolveErr)
+ }
+ if strings.TrimSpace(effective.AuthorizeURL) == "" {
+ effective.AuthorizeURL = strings.TrimSpace(metadata.AuthorizationEndpoint)
+ }
+ if strings.TrimSpace(effective.TokenURL) == "" {
+ effective.TokenURL = strings.TrimSpace(metadata.TokenEndpoint)
+ }
+ if strings.TrimSpace(effective.UserInfoURL) == "" {
+ effective.UserInfoURL = strings.TrimSpace(metadata.UserInfoEndpoint)
+ }
+ if strings.TrimSpace(effective.JWKSURL) == "" {
+ effective.JWKSURL = strings.TrimSpace(metadata.JWKSURI)
+ }
+ }
+
+ if strings.TrimSpace(effective.AuthorizeURL) == "" {
+ return config.OIDCConnectConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "oauth authorize url not configured")
+ }
+ if strings.TrimSpace(effective.TokenURL) == "" {
+ return config.OIDCConnectConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "oauth token url not configured")
+ }
+ if err := config.ValidateAbsoluteHTTPURL(effective.AuthorizeURL); err != nil {
+ return config.OIDCConnectConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "oauth authorize url invalid")
+ }
+ if err := config.ValidateAbsoluteHTTPURL(effective.TokenURL); err != nil {
+ return config.OIDCConnectConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "oauth token url invalid")
+ }
+ if v := strings.TrimSpace(effective.UserInfoURL); v != "" {
+ if err := config.ValidateAbsoluteHTTPURL(v); err != nil {
+ return config.OIDCConnectConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "oauth userinfo url invalid")
+ }
+ }
+ if effective.ValidateIDToken {
+ if strings.TrimSpace(effective.JWKSURL) == "" {
+ return config.OIDCConnectConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "oauth jwks url not configured")
+ }
+ if strings.TrimSpace(effective.AllowedSigningAlgs) == "" {
+ return config.OIDCConnectConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "oauth signing algs not configured")
+ }
+ }
+ if v := strings.TrimSpace(effective.JWKSURL); v != "" {
+ if err := config.ValidateAbsoluteHTTPURL(v); err != nil {
+ return config.OIDCConnectConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "oauth jwks url invalid")
+ }
+ }
+ if err := config.ValidateAbsoluteHTTPURL(effective.RedirectURL); err != nil {
+ return config.OIDCConnectConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "oauth redirect url invalid")
+ }
+ if err := config.ValidateFrontendRedirectURL(effective.FrontendRedirectURL); err != nil {
+ return config.OIDCConnectConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "oauth frontend redirect url invalid")
+ }
+
+ method := strings.ToLower(strings.TrimSpace(effective.TokenAuthMethod))
+ switch method {
+ case "", "client_secret_post", "client_secret_basic":
+ if strings.TrimSpace(effective.ClientSecret) == "" {
+ return config.OIDCConnectConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "oauth client secret not configured")
+ }
+ case "none":
+ if !effective.UsePKCE {
+ return config.OIDCConnectConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "oauth pkce must be enabled when token_auth_method=none")
+ }
+ default:
+ return config.OIDCConnectConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "oauth token_auth_method invalid")
+ }
+
+ return effective, nil
+}
+
+func scopesContainOpenID(scopes string) bool {
+ for _, scope := range strings.Fields(strings.ToLower(strings.TrimSpace(scopes))) {
+ if scope == "openid" {
+ return true
+ }
+ }
+ return false
+}
+
+type oidcProviderMetadata struct {
+ AuthorizationEndpoint string `json:"authorization_endpoint"`
+ TokenEndpoint string `json:"token_endpoint"`
+ UserInfoEndpoint string `json:"userinfo_endpoint"`
+ JWKSURI string `json:"jwks_uri"`
+}
+
+func oidcDefaultDiscoveryURL(issuerURL string) string {
+ issuerURL = strings.TrimSpace(issuerURL)
+ if issuerURL == "" {
+ return ""
+ }
+ return strings.TrimRight(issuerURL, "/") + "/.well-known/openid-configuration"
+}
+
+func oidcResolveProviderMetadata(ctx context.Context, discoveryURL string) (*oidcProviderMetadata, error) {
+ discoveryURL = strings.TrimSpace(discoveryURL)
+ if discoveryURL == "" {
+ return nil, fmt.Errorf("discovery url is empty")
+ }
+
+ resp, err := req.C().
+ SetTimeout(15*time.Second).
+ R().
+ SetContext(ctx).
+ SetHeader("Accept", "application/json").
+ Get(discoveryURL)
+ if err != nil {
+ return nil, fmt.Errorf("request discovery document: %w", err)
+ }
+ if !resp.IsSuccessState() {
+ return nil, fmt.Errorf("discovery request failed: status=%d", resp.StatusCode)
+ }
+
+ metadata := &oidcProviderMetadata{}
+ if err := json.Unmarshal(resp.Bytes(), metadata); err != nil {
+ return nil, fmt.Errorf("parse discovery document: %w", err)
+ }
+ return metadata, nil
+}
+
// GetStreamTimeoutSettings 获取流超时处理配置
func (s *SettingService) GetStreamTimeoutSettings(ctx context.Context) (*StreamTimeoutSettings, error) {
value, err := s.settingRepo.GetValue(ctx, SettingKeyStreamTimeoutSettings)
diff --git a/backend/internal/service/setting_service_oidc_config_test.go b/backend/internal/service/setting_service_oidc_config_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..3809b332bd107e2c6559a9b657014a8f6db80b77
--- /dev/null
+++ b/backend/internal/service/setting_service_oidc_config_test.go
@@ -0,0 +1,103 @@
+//go:build unit
+
+package service
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+
+ "github.com/Wei-Shaw/sub2api/internal/config"
+ "github.com/stretchr/testify/require"
+)
+
+type settingOIDCRepoStub struct {
+ values map[string]string
+}
+
+func (s *settingOIDCRepoStub) Get(ctx context.Context, key string) (*Setting, error) {
+ panic("unexpected Get call")
+}
+
+func (s *settingOIDCRepoStub) GetValue(ctx context.Context, key string) (string, error) {
+ panic("unexpected GetValue call")
+}
+
+func (s *settingOIDCRepoStub) Set(ctx context.Context, key, value string) error {
+ panic("unexpected Set call")
+}
+
+func (s *settingOIDCRepoStub) GetMultiple(ctx context.Context, keys []string) (map[string]string, error) {
+ out := make(map[string]string, len(keys))
+ for _, key := range keys {
+ if value, ok := s.values[key]; ok {
+ out[key] = value
+ }
+ }
+ return out, nil
+}
+
+func (s *settingOIDCRepoStub) SetMultiple(ctx context.Context, settings map[string]string) error {
+ panic("unexpected SetMultiple call")
+}
+
+func (s *settingOIDCRepoStub) GetAll(ctx context.Context) (map[string]string, error) {
+ panic("unexpected GetAll call")
+}
+
+func (s *settingOIDCRepoStub) Delete(ctx context.Context, key string) error {
+ panic("unexpected Delete call")
+}
+
+func TestGetOIDCConnectOAuthConfig_ResolvesEndpointsFromIssuerDiscovery(t *testing.T) {
+ var discoveryHits int
+ var baseURL string
+
+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if r.URL.Path != "/issuer/.well-known/openid-configuration" {
+ http.NotFound(w, r)
+ return
+ }
+ discoveryHits++
+ w.Header().Set("Content-Type", "application/json")
+ _, _ = w.Write([]byte(fmt.Sprintf(`{
+ "authorization_endpoint":"%s/issuer/protocol/openid-connect/auth",
+ "token_endpoint":"%s/issuer/protocol/openid-connect/token",
+ "userinfo_endpoint":"%s/issuer/protocol/openid-connect/userinfo",
+ "jwks_uri":"%s/issuer/protocol/openid-connect/certs"
+ }`, baseURL, baseURL, baseURL, baseURL)))
+ }))
+ defer srv.Close()
+ baseURL = srv.URL
+
+ cfg := &config.Config{
+ OIDC: config.OIDCConnectConfig{
+ Enabled: true,
+ ProviderName: "OIDC",
+ ClientID: "oidc-client",
+ ClientSecret: "oidc-secret",
+ IssuerURL: srv.URL + "/issuer",
+ RedirectURL: "https://example.com/api/v1/auth/oauth/oidc/callback",
+ FrontendRedirectURL: "/auth/oidc/callback",
+ Scopes: "openid email profile",
+ TokenAuthMethod: "client_secret_post",
+ ValidateIDToken: true,
+ AllowedSigningAlgs: "RS256",
+ ClockSkewSeconds: 120,
+ },
+ }
+
+ repo := &settingOIDCRepoStub{values: map[string]string{}}
+ svc := NewSettingService(repo, cfg)
+
+ got, err := svc.GetOIDCConnectOAuthConfig(context.Background())
+ require.NoError(t, err)
+ require.Equal(t, 1, discoveryHits)
+ require.Equal(t, srv.URL+"/issuer/.well-known/openid-configuration", got.DiscoveryURL)
+ require.Equal(t, srv.URL+"/issuer/protocol/openid-connect/auth", got.AuthorizeURL)
+ require.Equal(t, srv.URL+"/issuer/protocol/openid-connect/token", got.TokenURL)
+ require.Equal(t, srv.URL+"/issuer/protocol/openid-connect/userinfo", got.UserInfoURL)
+ require.Equal(t, srv.URL+"/issuer/protocol/openid-connect/certs", got.JWKSURL)
+}
diff --git a/backend/internal/service/setting_service_public_test.go b/backend/internal/service/setting_service_public_test.go
index b511cd29dcf86e979ac143d53cf2d55f5da83122..6dfa627cfa4c41a60acdd2b1260a280dde6c6f27 100644
--- a/backend/internal/service/setting_service_public_test.go
+++ b/backend/internal/service/setting_service_public_test.go
@@ -62,3 +62,18 @@ func TestSettingService_GetPublicSettings_ExposesRegistrationEmailSuffixWhitelis
require.NoError(t, err)
require.Equal(t, []string{"@example.com", "@foo.bar"}, settings.RegistrationEmailSuffixWhitelist)
}
+
+func TestSettingService_GetPublicSettings_ExposesTablePreferences(t *testing.T) {
+ repo := &settingPublicRepoStub{
+ values: map[string]string{
+ SettingKeyTableDefaultPageSize: "50",
+ SettingKeyTablePageSizeOptions: "[20,50,100]",
+ },
+ }
+ svc := NewSettingService(repo, &config.Config{})
+
+ settings, err := svc.GetPublicSettings(context.Background())
+ require.NoError(t, err)
+ require.Equal(t, 50, settings.TableDefaultPageSize)
+ require.Equal(t, []int{20, 50, 100}, settings.TablePageSizeOptions)
+}
diff --git a/backend/internal/service/setting_service_update_test.go b/backend/internal/service/setting_service_update_test.go
index 1de08611e22c466ab9da065219237a9e3c9f31ae..28c7ad022096a506756a5c396199ea273615e2de 100644
--- a/backend/internal/service/setting_service_update_test.go
+++ b/backend/internal/service/setting_service_update_test.go
@@ -202,3 +202,24 @@ func TestParseDefaultSubscriptions_NormalizesValues(t *testing.T) {
{GroupID: 12, ValidityDays: MaxValidityDays},
}, got)
}
+
+func TestSettingService_UpdateSettings_TablePreferences(t *testing.T) {
+ repo := &settingUpdateRepoStub{}
+ svc := NewSettingService(repo, &config.Config{})
+
+ err := svc.UpdateSettings(context.Background(), &SystemSettings{
+ TableDefaultPageSize: 50,
+ TablePageSizeOptions: []int{20, 50, 100},
+ })
+ require.NoError(t, err)
+ require.Equal(t, "50", repo.updates[SettingKeyTableDefaultPageSize])
+ require.Equal(t, "[20,50,100]", repo.updates[SettingKeyTablePageSizeOptions])
+
+ err = svc.UpdateSettings(context.Background(), &SystemSettings{
+ TableDefaultPageSize: 1000,
+ TablePageSizeOptions: []int{20, 100},
+ })
+ require.NoError(t, err)
+ require.Equal(t, "1000", repo.updates[SettingKeyTableDefaultPageSize])
+ require.Equal(t, "[20,100]", repo.updates[SettingKeyTablePageSizeOptions])
+}
diff --git a/backend/internal/service/settings_view.go b/backend/internal/service/settings_view.go
index fedb3f2fe1cb772cd49632835294d7dd7e5bb606..de92b796dc92c0d545fadb6285db03329a403151 100644
--- a/backend/internal/service/settings_view.go
+++ b/backend/internal/service/settings_view.go
@@ -31,6 +31,31 @@ type SystemSettings struct {
LinuxDoConnectClientSecretConfigured bool
LinuxDoConnectRedirectURL string
+ // Generic OIDC OAuth 登录
+ OIDCConnectEnabled bool
+ OIDCConnectProviderName string
+ OIDCConnectClientID string
+ OIDCConnectClientSecret string
+ OIDCConnectClientSecretConfigured bool
+ OIDCConnectIssuerURL string
+ OIDCConnectDiscoveryURL string
+ OIDCConnectAuthorizeURL string
+ OIDCConnectTokenURL string
+ OIDCConnectUserInfoURL string
+ OIDCConnectJWKSURL string
+ OIDCConnectScopes string
+ OIDCConnectRedirectURL string
+ OIDCConnectFrontendRedirectURL string
+ OIDCConnectTokenAuthMethod string
+ OIDCConnectUsePKCE bool
+ OIDCConnectValidateIDToken bool
+ OIDCConnectAllowedSigningAlgs string
+ OIDCConnectClockSkewSeconds int
+ OIDCConnectRequireEmailVerified bool
+ OIDCConnectUserInfoEmailPath string
+ OIDCConnectUserInfoIDPath string
+ OIDCConnectUserInfoUsernamePath string
+
SiteName string
SiteLogo string
SiteSubtitle string
@@ -41,6 +66,8 @@ type SystemSettings struct {
HideCcsImportButton bool
PurchaseSubscriptionEnabled bool
PurchaseSubscriptionURL string
+ TableDefaultPageSize int
+ TablePageSizeOptions []int
CustomMenuItems string // JSON array of custom menu items
CustomEndpoints string // JSON array of custom endpoints
@@ -107,12 +134,17 @@ type PublicSettings struct {
PurchaseSubscriptionEnabled bool
PurchaseSubscriptionURL string
+ TableDefaultPageSize int
+ TablePageSizeOptions []int
CustomMenuItems string // JSON array of custom menu items
CustomEndpoints string // JSON array of custom endpoints
- LinuxDoOAuthEnabled bool
- BackendModeEnabled bool
- Version string
+ LinuxDoOAuthEnabled bool
+ BackendModeEnabled bool
+ OIDCOAuthEnabled bool
+ OIDCOAuthProviderName string
+ PaymentEnabled bool
+ Version string
}
// StreamTimeoutSettings 流超时处理配置(仅控制超时后的处理方式,超时判定由网关配置控制)
diff --git a/backend/internal/service/wire.go b/backend/internal/service/wire.go
index d66d8cff86e574611ea46d6c30a034032f65afb9..a8ece8a38cecf90aece40a94e9ed5b24f33b5a2a 100644
--- a/backend/internal/service/wire.go
+++ b/backend/internal/service/wire.go
@@ -5,7 +5,9 @@ import (
"database/sql"
"time"
+ dbent "github.com/Wei-Shaw/sub2api/ent"
"github.com/Wei-Shaw/sub2api/internal/config"
+ "github.com/Wei-Shaw/sub2api/internal/payment"
"github.com/Wei-Shaw/sub2api/internal/pkg/logger"
"github.com/google/wire"
"github.com/redis/go-redis/v9"
@@ -460,4 +462,20 @@ var ProviderSet = wire.NewSet(
NewGroupCapacityService,
NewChannelService,
NewModelPricingResolver,
+ ProvidePaymentConfigService,
+ NewPaymentService,
+ ProvidePaymentOrderExpiryService,
)
+
+// ProvidePaymentConfigService wraps NewPaymentConfigService to accept the named
+// payment.EncryptionKey type instead of raw []byte, avoiding Wire ambiguity.
+func ProvidePaymentConfigService(entClient *dbent.Client, settingRepo SettingRepository, key payment.EncryptionKey) *PaymentConfigService {
+ return NewPaymentConfigService(entClient, settingRepo, []byte(key))
+}
+
+// ProvidePaymentOrderExpiryService creates and starts PaymentOrderExpiryService.
+func ProvidePaymentOrderExpiryService(paymentSvc *PaymentService) *PaymentOrderExpiryService {
+ svc := NewPaymentOrderExpiryService(paymentSvc, 60*time.Second)
+ svc.Start()
+ return svc
+}
diff --git a/backend/migrations/091_add_group_messages_dispatch_model_config.sql b/backend/migrations/091_add_group_messages_dispatch_model_config.sql
new file mode 100644
index 0000000000000000000000000000000000000000..8ddfcb0f72204888fb1ba0b9d342d444484acc51
--- /dev/null
+++ b/backend/migrations/091_add_group_messages_dispatch_model_config.sql
@@ -0,0 +1,2 @@
+ALTER TABLE groups
+ADD COLUMN IF NOT EXISTS messages_dispatch_model_config JSONB NOT NULL DEFAULT '{}'::jsonb;
diff --git a/backend/migrations/092_payment_orders.sql b/backend/migrations/092_payment_orders.sql
new file mode 100644
index 0000000000000000000000000000000000000000..036e4dedc0dcb65d902e36f9ac0711ef7ce61515
--- /dev/null
+++ b/backend/migrations/092_payment_orders.sql
@@ -0,0 +1,47 @@
+CREATE TABLE IF NOT EXISTS payment_orders (
+ id BIGSERIAL PRIMARY KEY,
+ user_id BIGINT NOT NULL,
+ user_email VARCHAR(255) NOT NULL DEFAULT '',
+ user_name VARCHAR(100) NOT NULL DEFAULT '',
+ user_notes TEXT,
+ amount DECIMAL(20,2) NOT NULL,
+ pay_amount DECIMAL(20,2) NOT NULL,
+ fee_rate DECIMAL(10,4) NOT NULL DEFAULT 0,
+ recharge_code VARCHAR(64) NOT NULL DEFAULT '',
+ payment_type VARCHAR(30) NOT NULL DEFAULT '',
+ payment_trade_no VARCHAR(128) NOT NULL DEFAULT '',
+ pay_url TEXT,
+ qr_code TEXT,
+ qr_code_img TEXT,
+ order_type VARCHAR(20) NOT NULL DEFAULT 'balance',
+ plan_id BIGINT,
+ subscription_group_id BIGINT,
+ subscription_days INT,
+ provider_instance_id VARCHAR(64),
+ status VARCHAR(30) NOT NULL DEFAULT 'PENDING',
+ refund_amount DECIMAL(20,2) NOT NULL DEFAULT 0,
+ refund_reason TEXT,
+ refund_at TIMESTAMPTZ,
+ force_refund BOOLEAN NOT NULL DEFAULT FALSE,
+ refund_requested_at TIMESTAMPTZ,
+ refund_request_reason TEXT,
+ refund_requested_by VARCHAR(20),
+ expires_at TIMESTAMPTZ NOT NULL,
+ paid_at TIMESTAMPTZ,
+ completed_at TIMESTAMPTZ,
+ failed_at TIMESTAMPTZ,
+ failed_reason TEXT,
+ client_ip VARCHAR(50) NOT NULL DEFAULT '',
+ src_host VARCHAR(255) NOT NULL DEFAULT '',
+ src_url TEXT,
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
+);
+-- Indexes
+CREATE INDEX IF NOT EXISTS idx_payment_orders_user_id ON payment_orders(user_id);
+CREATE INDEX IF NOT EXISTS idx_payment_orders_status ON payment_orders(status);
+CREATE INDEX IF NOT EXISTS idx_payment_orders_expires_at ON payment_orders(expires_at);
+CREATE INDEX IF NOT EXISTS idx_payment_orders_created_at ON payment_orders(created_at);
+CREATE INDEX IF NOT EXISTS idx_payment_orders_paid_at ON payment_orders(paid_at);
+CREATE INDEX IF NOT EXISTS idx_payment_orders_type_paid ON payment_orders(payment_type, paid_at);
+CREATE INDEX IF NOT EXISTS idx_payment_orders_order_type ON payment_orders(order_type);
diff --git a/backend/migrations/093_payment_audit_logs.sql b/backend/migrations/093_payment_audit_logs.sql
new file mode 100644
index 0000000000000000000000000000000000000000..d05b15ef835e67b5a62f4c18aa03146f97130ca2
--- /dev/null
+++ b/backend/migrations/093_payment_audit_logs.sql
@@ -0,0 +1,9 @@
+CREATE TABLE IF NOT EXISTS payment_audit_logs (
+ id BIGSERIAL PRIMARY KEY,
+ order_id VARCHAR(64) NOT NULL,
+ action VARCHAR(50) NOT NULL,
+ detail TEXT NOT NULL DEFAULT '',
+ operator VARCHAR(100) NOT NULL DEFAULT 'system',
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
+);
+CREATE INDEX IF NOT EXISTS idx_payment_audit_logs_order_id ON payment_audit_logs(order_id);
diff --git a/backend/migrations/094_removed_payment_channels.sql b/backend/migrations/094_removed_payment_channels.sql
new file mode 100644
index 0000000000000000000000000000000000000000..cb20234795f0c3ab4535f2076c8d5585d775d4c8
--- /dev/null
+++ b/backend/migrations/094_removed_payment_channels.sql
@@ -0,0 +1,4 @@
+-- Migration 092: payment_channels table was removed before release.
+-- This file is a no-op placeholder to maintain migration numbering continuity.
+-- The payment system now uses the existing channels table (migration 081).
+SELECT 1;
diff --git a/backend/migrations/095_subscription_plans.sql b/backend/migrations/095_subscription_plans.sql
new file mode 100644
index 0000000000000000000000000000000000000000..541d8f0c89aba0943415a26674d9b98a22b80fd5
--- /dev/null
+++ b/backend/migrations/095_subscription_plans.sql
@@ -0,0 +1,18 @@
+CREATE TABLE IF NOT EXISTS subscription_plans (
+ id BIGSERIAL PRIMARY KEY,
+ group_id BIGINT NOT NULL,
+ name VARCHAR(100) NOT NULL,
+ description TEXT NOT NULL DEFAULT '',
+ price DECIMAL(20,2) NOT NULL,
+ original_price DECIMAL(20,2),
+ validity_days INT NOT NULL DEFAULT 30,
+ validity_unit VARCHAR(10) NOT NULL DEFAULT 'day',
+ features TEXT NOT NULL DEFAULT '',
+ product_name VARCHAR(100) NOT NULL DEFAULT '',
+ for_sale BOOLEAN NOT NULL DEFAULT TRUE,
+ sort_order INT NOT NULL DEFAULT 0,
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
+);
+CREATE INDEX IF NOT EXISTS idx_subscription_plans_group_id ON subscription_plans(group_id);
+CREATE INDEX IF NOT EXISTS idx_subscription_plans_for_sale ON subscription_plans(for_sale);
diff --git a/backend/migrations/096_payment_provider_instances.sql b/backend/migrations/096_payment_provider_instances.sql
new file mode 100644
index 0000000000000000000000000000000000000000..bedd75df603408a1a47ab4cd60ff79487c117e14
--- /dev/null
+++ b/backend/migrations/096_payment_provider_instances.sql
@@ -0,0 +1,15 @@
+CREATE TABLE IF NOT EXISTS payment_provider_instances (
+ id BIGSERIAL PRIMARY KEY,
+ provider_key VARCHAR(30) NOT NULL,
+ name VARCHAR(100) NOT NULL DEFAULT '',
+ config TEXT NOT NULL,
+ supported_types VARCHAR(200) NOT NULL DEFAULT '',
+ enabled BOOLEAN NOT NULL DEFAULT TRUE,
+ sort_order INT NOT NULL DEFAULT 0,
+ limits TEXT NOT NULL DEFAULT '',
+ refund_enabled BOOLEAN NOT NULL DEFAULT FALSE,
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
+);
+CREATE INDEX IF NOT EXISTS idx_payment_provider_instances_provider_key ON payment_provider_instances(provider_key);
+CREATE INDEX IF NOT EXISTS idx_payment_provider_instances_enabled ON payment_provider_instances(enabled);
diff --git a/backend/migrations/098_migrate_purchase_subscription_to_custom_menu.sql b/backend/migrations/098_migrate_purchase_subscription_to_custom_menu.sql
new file mode 100644
index 0000000000000000000000000000000000000000..1864459ecca0abfb17c6d773b224f1feeb46f933
--- /dev/null
+++ b/backend/migrations/098_migrate_purchase_subscription_to_custom_menu.sql
@@ -0,0 +1,70 @@
+-- 096_migrate_purchase_subscription_to_custom_menu.sql
+--
+-- Migrates the legacy purchase_subscription_url setting into custom_menu_items.
+-- After migration, purchase_subscription_enabled is set to "false" and
+-- purchase_subscription_url is cleared.
+--
+-- Idempotent: skips if custom_menu_items already contains
+-- "migrated_purchase_subscription".
+
+DO $$
+DECLARE
+ v_enabled text;
+ v_url text;
+ v_raw text;
+ v_items jsonb;
+ v_new_item jsonb;
+BEGIN
+ -- Read legacy settings
+ SELECT value INTO v_enabled
+ FROM settings WHERE key = 'purchase_subscription_enabled';
+ SELECT value INTO v_url
+ FROM settings WHERE key = 'purchase_subscription_url';
+
+ -- Skip if not enabled or URL is empty
+ IF COALESCE(v_enabled, '') <> 'true' OR COALESCE(TRIM(v_url), '') = '' THEN
+ RETURN;
+ END IF;
+
+ -- Read current custom_menu_items
+ SELECT value INTO v_raw
+ FROM settings WHERE key = 'custom_menu_items';
+
+ IF COALESCE(v_raw, '') = '' OR v_raw = 'null' THEN
+ v_items := '[]'::jsonb;
+ ELSE
+ v_items := v_raw::jsonb;
+ END IF;
+
+ -- Skip if already migrated (item with id "migrated_purchase_subscription" exists)
+ IF EXISTS (
+ SELECT 1 FROM jsonb_array_elements(v_items) elem
+ WHERE elem ->> 'id' = 'migrated_purchase_subscription'
+ ) THEN
+ RETURN;
+ END IF;
+
+ -- Build the new menu item
+ v_new_item := jsonb_build_object(
+ 'id', 'migrated_purchase_subscription',
+ 'label', 'Purchase',
+ 'icon_svg', '',
+ 'url', TRIM(v_url),
+ 'visibility', 'user',
+ 'sort_order', 100
+ );
+
+ -- Append to array
+ v_items := v_items || jsonb_build_array(v_new_item);
+
+ -- Upsert custom_menu_items
+ INSERT INTO settings (key, value)
+ VALUES ('custom_menu_items', v_items::text)
+ ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value;
+
+ -- Clear legacy settings
+ UPDATE settings SET value = 'false' WHERE key = 'purchase_subscription_enabled';
+ UPDATE settings SET value = '' WHERE key = 'purchase_subscription_url';
+
+ RAISE NOTICE '[migration-096] Migrated purchase_subscription_url (%) to custom_menu_items', v_url;
+END $$;
diff --git a/backend/migrations/099_fix_migrated_purchase_menu_label_icon.sql b/backend/migrations/099_fix_migrated_purchase_menu_label_icon.sql
new file mode 100644
index 0000000000000000000000000000000000000000..5361ad816606bbf4133491a8e5d195364b5ce7c8
--- /dev/null
+++ b/backend/migrations/099_fix_migrated_purchase_menu_label_icon.sql
@@ -0,0 +1,51 @@
+-- 097_fix_migrated_purchase_menu_label_icon.sql
+--
+-- Fixes the custom menu item created by migration 096: updates the label
+-- from hardcoded English "Purchase" to "充值/订阅", and sets the icon_svg
+-- to a credit-card SVG matching the sidebar CreditCardIcon.
+--
+-- Idempotent: only modifies items where id = 'migrated_purchase_subscription'.
+
+DO $$
+DECLARE
+ v_raw text;
+ v_items jsonb;
+ v_idx int;
+ v_icon text;
+ v_elem jsonb;
+ v_i int := 0;
+BEGIN
+ SELECT value INTO v_raw
+ FROM settings WHERE key = 'custom_menu_items';
+
+ IF COALESCE(v_raw, '') = '' OR v_raw = 'null' THEN
+ RETURN;
+ END IF;
+
+ v_items := v_raw::jsonb;
+
+ -- Find the index of the migrated item by iterating the array
+ v_idx := NULL;
+ FOR v_elem IN SELECT jsonb_array_elements(v_items) LOOP
+ IF v_elem ->> 'id' = 'migrated_purchase_subscription' THEN
+ v_idx := v_i;
+ EXIT;
+ END IF;
+ v_i := v_i + 1;
+ END LOOP;
+
+ IF v_idx IS NULL THEN
+ RETURN; -- item not found, nothing to fix
+ END IF;
+
+ -- Credit card SVG (Heroicons outline, matches CreditCardIcon in AppSidebar)
+ v_icon := ' ';
+
+ -- Update label and icon_svg
+ v_items := jsonb_set(v_items, ARRAY[v_idx::text, 'label'], '"充值/订阅"'::jsonb);
+ v_items := jsonb_set(v_items, ARRAY[v_idx::text, 'icon_svg'], to_jsonb(v_icon));
+
+ UPDATE settings SET value = v_items::text WHERE key = 'custom_menu_items';
+
+ RAISE NOTICE '[migration-097] Fixed migrated_purchase_subscription: label=充值/订阅, icon=CreditCard SVG';
+END $$;
diff --git a/backend/migrations/100_remove_easypay_from_enabled_payment_types.sql b/backend/migrations/100_remove_easypay_from_enabled_payment_types.sql
new file mode 100644
index 0000000000000000000000000000000000000000..8128ed09ff4cdbacd77bd65fe53d039e1513e79d
--- /dev/null
+++ b/backend/migrations/100_remove_easypay_from_enabled_payment_types.sql
@@ -0,0 +1,17 @@
+-- 098_remove_easypay_from_enabled_payment_types.sql
+--
+-- Removes "easypay" from ENABLED_PAYMENT_TYPES setting.
+-- "easypay" is a provider key, not a payment type. Valid payment types
+-- are: alipay, wxpay, alipay_direct, wxpay_direct, stripe.
+--
+-- Idempotent: safe to run multiple times.
+
+UPDATE settings
+ SET value = array_to_string(
+ array_remove(
+ string_to_array(value, ','),
+ 'easypay'
+ ), ','
+ )
+ WHERE key = 'ENABLED_PAYMENT_TYPES'
+ AND value LIKE '%easypay%';
diff --git a/backend/migrations/101_add_payment_mode.sql b/backend/migrations/101_add_payment_mode.sql
new file mode 100644
index 0000000000000000000000000000000000000000..eeb6ba7b2ff44b0250f200a82ffa01b606845df2
--- /dev/null
+++ b/backend/migrations/101_add_payment_mode.sql
@@ -0,0 +1,16 @@
+-- Add payment_mode field to payment_provider_instances
+-- Values: 'redirect' (hosted page redirect), 'api' (API call for QR/payurl), '' (default/N/A)
+ALTER TABLE payment_provider_instances ADD COLUMN IF NOT EXISTS payment_mode VARCHAR(20) NOT NULL DEFAULT '';
+
+-- Migrate existing data: easypay instances with 'easypay' in supported_types → redirect mode
+-- Remove 'easypay' from supported_types and set payment_mode = 'redirect'
+UPDATE payment_provider_instances
+SET payment_mode = 'redirect',
+ supported_types = TRIM(BOTH ',' FROM REPLACE(REPLACE(REPLACE(
+ supported_types, 'easypay,', ''), ',easypay', ''), 'easypay', ''))
+WHERE provider_key = 'easypay' AND supported_types LIKE '%easypay%';
+
+-- EasyPay instances without 'easypay' in supported_types → api mode
+UPDATE payment_provider_instances
+SET payment_mode = 'api'
+WHERE provider_key = 'easypay' AND payment_mode = '';
diff --git a/backend/migrations/102_add_out_trade_no_to_payment_orders.sql b/backend/migrations/102_add_out_trade_no_to_payment_orders.sql
new file mode 100644
index 0000000000000000000000000000000000000000..896c3c954de797a99a18113bf1972dd30a0fbe26
--- /dev/null
+++ b/backend/migrations/102_add_out_trade_no_to_payment_orders.sql
@@ -0,0 +1,6 @@
+-- 100_add_out_trade_no_to_payment_orders.sql
+-- Adds out_trade_no column for external order ID used with payment providers.
+-- Allows webhook handlers to look up orders by external ID instead of embedding DB ID.
+
+ALTER TABLE payment_orders ADD COLUMN IF NOT EXISTS out_trade_no VARCHAR(64) NOT NULL DEFAULT '';
+CREATE INDEX IF NOT EXISTS paymentorder_out_trade_no ON payment_orders (out_trade_no);
diff --git a/deploy/codex-instructions.md.tmpl b/deploy/codex-instructions.md.tmpl
new file mode 100644
index 0000000000000000000000000000000000000000..87ad0a3d843d4792d03ee3a3bd357c33b4996a0f
--- /dev/null
+++ b/deploy/codex-instructions.md.tmpl
@@ -0,0 +1,5 @@
+You are Codex, based on GPT-5. You are running as a coding agent in the Codex CLI on a user's computer.
+
+{{ if .ExistingInstructions }}
+{{ .ExistingInstructions }}
+{{ end }}
diff --git a/deploy/config.example.yaml b/deploy/config.example.yaml
index 8f60acd5edc971d487512eb9bd0238ecd378f2e4..358f6a31d9db662dbe12fa74d9a832ccbe5a16ee 100644
--- a/deploy/config.example.yaml
+++ b/deploy/config.example.yaml
@@ -202,6 +202,32 @@ gateway:
#
# 注意:开启后会影响所有客户端的行为(不仅限于 VS Code / Codex CLI),请谨慎开启。
force_codex_cli: false
+ # Optional: template file used to build the final top-level Codex `instructions`.
+ # 可选:用于构建最终 Codex 顶层 `instructions` 的模板文件路径。
+ #
+ # This is applied on the `/v1/messages -> Responses/Codex` conversion path,
+ # after Claude `system` has already been normalized into Codex `instructions`.
+ # 该模板作用于 `/v1/messages -> Responses/Codex` 转换链路,且发生在 Claude `system`
+ # 已经被归一化为 Codex `instructions` 之后。
+ #
+ # The template can reference:
+ # 模板可引用:
+ # - {{ .ExistingInstructions }} : converted client instructions/system
+ # - {{ .OriginalModel }} : original requested model
+ # - {{ .NormalizedModel }} : normalized routing model
+ # - {{ .BillingModel }} : billing model
+ # - {{ .UpstreamModel }} : final upstream model
+ #
+ # If you want to preserve client system prompts, keep {{ .ExistingInstructions }}
+ # somewhere in the template. If omitted, the template output fully replaces it.
+ # 如需保留客户端 system 提示词,请在模板中显式包含 {{ .ExistingInstructions }}。
+ # 若省略,则模板输出会完全覆盖它。
+ #
+ # Docker users can mount a host file to /app/data/codex-instructions.md.tmpl
+ # and point this field there.
+ # Docker 用户可将宿主机文件挂载到 /app/data/codex-instructions.md.tmpl,
+ # 然后把本字段指向该路径。
+ forced_codex_instructions_template_file: ""
# OpenAI 透传模式是否放行客户端超时头(如 x-stainless-timeout)
# 默认 false:过滤超时头,降低上游提前断流风险。
openai_passthrough_allow_timeout_headers: false
@@ -820,6 +846,46 @@ linuxdo_connect:
userinfo_id_path: ""
userinfo_username_path: ""
+# =============================================================================
+# Generic OIDC OAuth Login (SSO)
+# 通用 OIDC OAuth 登录(用于 Sub2API 用户登录)
+# =============================================================================
+oidc_connect:
+ enabled: false
+ provider_name: "OIDC"
+ client_id: ""
+ client_secret: ""
+ # 例如: "https://keycloak.example.com/realms/myrealm"
+ issuer_url: ""
+ # 可选: OIDC Discovery URL。为空时可手动填写 authorize/token/userinfo/jwks
+ discovery_url: ""
+ authorize_url: ""
+ token_url: ""
+ # 可选(仅补充 email/username,不用于 sub 可信绑定)
+ userinfo_url: ""
+ # validate_id_token=true 时必填
+ jwks_url: ""
+ scopes: "openid email profile"
+ # 示例: "https://your-domain.com/api/v1/auth/oauth/oidc/callback"
+ redirect_url: ""
+ # 安全提示:
+ # - 建议使用同源相对路径(以 / 开头),避免把 token 重定向到意外的第三方域名
+ # - 该地址不应包含 #fragment(本实现使用 URL fragment 传递 access_token)
+ frontend_redirect_url: "/auth/oidc/callback"
+ token_auth_method: "client_secret_post" # client_secret_post | client_secret_basic | none
+ # 注意:当 token_auth_method=none(public client)时,必须启用 PKCE
+ use_pkce: false
+ # 开启后强制校验 id_token 的签名和 claims(推荐)
+ validate_id_token: true
+ allowed_signing_algs: "RS256,ES256,PS256"
+ # 允许的时钟偏移(秒)
+ clock_skew_seconds: 120
+ # 若 Provider 返回 email_verified=false,是否拒绝登录
+ require_email_verified: false
+ userinfo_email_path: ""
+ userinfo_id_path: ""
+ userinfo_username_path: ""
+
# =============================================================================
# Default Settings
# 默认设置
diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml
index a0bc1a606164f51c190213a6e134f563d713f97e..3a714260f20cf9125afe9e0da75f2d38251b13ae 100644
--- a/deploy/docker-compose.yml
+++ b/deploy/docker-compose.yml
@@ -31,6 +31,10 @@ services:
# Optional: Mount custom config.yaml (uncomment and create the file first)
# Copy config.example.yaml to config.yaml, modify it, then uncomment:
# - ./config.yaml:/app/data/config.yaml
+ # Optional: Mount a custom Codex instructions template file, then point
+ # gateway.forced_codex_instructions_template_file at /app/data/codex-instructions.md.tmpl
+ # in config.yaml.
+ # - ./codex-instructions.md.tmpl:/app/data/codex-instructions.md.tmpl:ro
environment:
# =======================================================================
# Auto Setup (REQUIRED for Docker deployment)
@@ -146,7 +150,17 @@ services:
networks:
- sub2api-network
healthcheck:
- test: ["CMD", "wget", "-q", "-T", "5", "-O", "/dev/null", "http://localhost:8080/health"]
+ test:
+ [
+ "CMD",
+ "wget",
+ "-q",
+ "-T",
+ "5",
+ "-O",
+ "/dev/null",
+ "http://localhost:8080/health",
+ ]
interval: 30s
timeout: 10s
retries: 3
@@ -177,11 +191,17 @@ services:
networks:
- sub2api-network
healthcheck:
- test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-sub2api} -d ${POSTGRES_DB:-sub2api}"]
+ test:
+ [
+ "CMD-SHELL",
+ "pg_isready -U ${POSTGRES_USER:-sub2api} -d ${POSTGRES_DB:-sub2api}",
+ ]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
+ ports:
+ - 5432:5432
# 注意:不暴露端口到宿主机,应用通过内部网络连接
# 如需调试,可临时添加:ports: ["127.0.0.1:5433:5432"]
@@ -199,12 +219,12 @@ services:
volumes:
- redis_data:/data
command: >
- sh -c '
- redis-server
- --save 60 1
- --appendonly yes
- --appendfsync everysec
- ${REDIS_PASSWORD:+--requirepass "$REDIS_PASSWORD"}'
+ sh -c '
+ redis-server
+ --save 60 1
+ --appendonly yes
+ --appendfsync everysec
+ ${REDIS_PASSWORD:+--requirepass "$REDIS_PASSWORD"}'
environment:
- TZ=${TZ:-Asia/Shanghai}
# REDISCLI_AUTH is used by redis-cli for authentication (safer than -a flag)
@@ -217,7 +237,8 @@ services:
timeout: 5s
retries: 5
start_period: 5s
-
+ ports:
+ - 6379:6379
# =============================================================================
# Volumes
# =============================================================================
diff --git a/docs/PAYMENT.md b/docs/PAYMENT.md
new file mode 100644
index 0000000000000000000000000000000000000000..b66a791c5f8392174cf2ea1b821c6603bfec10a5
--- /dev/null
+++ b/docs/PAYMENT.md
@@ -0,0 +1,273 @@
+# Payment System Configuration Guide
+
+Sub2API has a built-in payment system that enables user self-service top-up without deploying a separate payment service.
+
+---
+
+## Table of Contents
+
+- [Supported Payment Methods](#supported-payment-methods)
+- [Quick Start](#quick-start)
+- [System Settings](#system-settings)
+- [Provider Configuration](#provider-configuration)
+- [Provider Instance Management](#provider-instance-management)
+- [Webhook Configuration](#webhook-configuration)
+- [Payment Flow](#payment-flow)
+- [Migrating from Sub2ApiPay](#migrating-from-sub2apipay)
+
+---
+
+## Supported Payment Methods
+
+| Provider | Payment Methods | Description |
+|----------|----------------|-------------|
+| **EasyPay** | Alipay, WeChat Pay | Third-party aggregation via EasyPay protocol |
+| **Alipay (Direct)** | PC Page Pay, H5 Mobile Pay | Direct integration with Alipay Open Platform, auto-switches by device |
+| **WeChat Pay (Direct)** | Native QR Code, H5 Pay | Direct integration with WeChat Pay APIv3, mobile-first H5 |
+| **Stripe** | Card, Alipay, WeChat Pay, Link, etc. | International payments, multi-currency support |
+
+> Alipay/WeChat Pay direct and EasyPay can coexist. Direct channels connect to payment APIs directly with lower fees; EasyPay aggregates through third-party platforms with easier setup.
+
+> **EasyPay Recommendation**: [ZPay](https://z-pay.cn/?uid=23808) (`https://z-pay.cn/?uid=23808`) is recommended as an EasyPay provider (link contains the referral code of [Sub2ApiPay](https://github.com/touwaeriol/sub2apipay) original author [@touwaeriol](https://github.com/touwaeriol) — feel free to remove it). ZPay supports **individual users** (no business license required) with up to 10,000 CNY daily transactions; business-licensed accounts have no limit. Please evaluate the security, reliability, and compliance of any third-party payment provider on your own — this project does not endorse or guarantee any of them.
+
+---
+
+## Quick Start
+
+1. Go to Admin Dashboard → **Settings** → **Payment Settings** tab
+2. Enable **Payment**
+3. Configure basic parameters (amount range, timeout, etc.)
+4. Add at least one provider instance in **Provider Management**
+5. Users can now top up from the frontend
+
+---
+
+## System Settings
+
+Configure the following in Admin Dashboard **Settings → Payment Settings**:
+
+### Basic Settings
+
+| Setting | Description | Default |
+|---------|-------------|---------|
+| **Enable Payment** | Enable or disable the payment system | Off |
+| **Product Name Prefix** | Prefix shown on payment page | - |
+| **Product Name Suffix** | Suffix (e.g., "Credits") | - |
+| **Minimum Amount** | Minimum single top-up amount | 1 |
+| **Maximum Amount** | Maximum single top-up amount (empty = unlimited) | - |
+| **Daily Limit** | Per-user daily cumulative limit (empty = unlimited) | - |
+| **Order Timeout** | Order timeout in minutes (minimum 1) | 5 |
+| **Max Pending Orders** | Maximum concurrent pending orders per user | 3 |
+| **Load Balance Strategy** | Strategy for selecting provider instances | Least Amount |
+
+### Load Balance Strategies
+
+| Strategy | Description |
+|----------|-------------|
+| **Round Robin** | Distribute orders to instances in rotation |
+| **Least Amount** | Prefer instances with the lowest daily cumulative amount |
+
+### Cancel Rate Limiting
+
+Prevents users from repeatedly creating and canceling orders:
+
+| Setting | Description |
+|---------|-------------|
+| **Enable Limit** | Toggle |
+| **Window Mode** | Sliding / Fixed window |
+| **Time Window** | Window duration |
+| **Window Unit** | Minutes / Hours |
+| **Max Cancels** | Maximum cancellations allowed within the window |
+
+### Help Information
+
+| Setting | Description |
+|---------|-------------|
+| **Help Image** | Customer service QR code or help image (supports upload) |
+| **Help Text** | Instructions displayed on the payment page |
+
+---
+
+## Provider Configuration
+
+Each provider type requires different credentials. Select the type when adding a new provider instance in **Provider Management → Add Provider**.
+
+> **Callback URLs are auto-generated**: When adding a provider, the Notify URL and Return URL are automatically constructed from your site domain. You only need to confirm the domain is correct.
+
+### EasyPay
+
+Compatible with any payment service that implements the EasyPay protocol.
+
+| Parameter | Description | Required |
+|-----------|-------------|----------|
+| **Merchant ID (PID)** | EasyPay merchant ID | Yes |
+| **Merchant Key (PKey)** | EasyPay merchant secret key | Yes |
+| **API Base URL** | EasyPay API base address | Yes |
+| **Alipay Channel ID** | Specify Alipay channel (optional) | No |
+| **WeChat Channel ID** | Specify WeChat channel (optional) | No |
+
+### Alipay (Direct)
+
+Direct integration with Alipay Open Platform. Supports PC page pay and H5 mobile pay.
+
+| Parameter | Description | Required |
+|-----------|-------------|----------|
+| **AppID** | Alipay application AppID | Yes |
+| **Private Key** | RSA2 application private key | Yes |
+| **Alipay Public Key** | Alipay public key | Yes |
+
+### WeChat Pay (Direct)
+
+Direct integration with WeChat Pay APIv3. Supports Native QR code and H5 payment.
+
+| Parameter | Description | Required |
+|-----------|-------------|----------|
+| **AppID** | WeChat Pay AppID | Yes |
+| **Merchant ID (MchID)** | WeChat Pay merchant ID | Yes |
+| **Merchant API Private Key** | Merchant API private key (PEM format) | Yes |
+| **APIv3 Key** | 32-byte APIv3 key | Yes |
+| **WeChat Pay Public Key** | WeChat Pay public key (PEM format) | Yes |
+| **WeChat Pay Public Key ID** | WeChat Pay public key ID | No |
+| **Certificate Serial Number** | Merchant certificate serial number | No |
+
+### Stripe
+
+International payment platform supporting multiple payment methods and currencies.
+
+| Parameter | Description | Required |
+|-----------|-------------|----------|
+| **Secret Key** | Stripe secret key (`sk_live_...` or `sk_test_...`) | Yes |
+| **Publishable Key** | Stripe publishable key (`pk_live_...` or `pk_test_...`) | Yes |
+| **Webhook Secret** | Stripe Webhook signing secret (`whsec_...`) | Yes |
+
+---
+
+## Provider Instance Management
+
+You can create **multiple instances** of the same provider type for load balancing and risk control:
+
+- **Multi-instance load balancing** — Distribute orders via round-robin or least-amount strategy
+- **Independent limits** — Each instance can have its own min/max amount and daily limit
+- **Independent toggle** — Enable/disable individual instances without affecting others
+- **Refund control** — Enable or disable refunds per instance
+- **Payment methods** — Each instance can support a subset of payment methods
+- **Ordering** — Drag to reorder instances
+
+### Instance Limit Configuration
+
+Each instance supports these limits:
+
+| Limit | Description |
+|-------|-------------|
+| **Minimum Amount** | Minimum order amount accepted by this instance |
+| **Maximum Amount** | Maximum order amount accepted by this instance |
+| **Daily Limit** | Daily cumulative transaction limit for this instance |
+
+> During load balancing, instances that exceed their limits are automatically skipped.
+
+---
+
+## Webhook Configuration
+
+Payment callbacks are essential for the payment system to work correctly.
+
+### Callback URL Format
+
+When adding a provider, the system auto-generates callback URLs from your site domain:
+
+| Provider | Callback Path |
+|----------|-------------|
+| **EasyPay** | `https://your-domain.com/api/v1/payment/webhook/easypay` |
+| **Alipay (Direct)** | `https://your-domain.com/api/v1/payment/webhook/alipay` |
+| **WeChat Pay (Direct)** | `https://your-domain.com/api/v1/payment/webhook/wxpay` |
+| **Stripe** | `https://your-domain.com/api/v1/payment/webhook/stripe` |
+
+> Replace `your-domain.com` with your actual domain. For EasyPay / Alipay / WeChat Pay, the callback URL is auto-filled when adding the provider — no manual configuration needed.
+
+### Stripe Webhook Setup
+
+1. Log in to [Stripe Dashboard](https://dashboard.stripe.com/)
+2. Go to **Developers → Webhooks**
+3. Add an endpoint with the callback URL
+4. Subscribe to events: `payment_intent.succeeded`, `payment_intent.payment_failed`
+5. Copy the generated Webhook Secret (`whsec_...`) to your provider configuration
+
+### Important Notes
+
+- Callback URLs must use **HTTPS** (required by Stripe, strongly recommended for others)
+- Ensure your firewall allows callback requests from payment platforms
+- The system automatically verifies callback signatures to prevent forgery
+- Balance top-up is processed automatically upon successful payment — no manual intervention needed
+
+---
+
+## Payment Flow
+
+```
+User selects amount and payment method
+ │
+ ▼
+ Create Order (PENDING)
+ ├─ Validate amount range, pending order count, daily limit
+ ├─ Load balance to select provider instance
+ └─ Call provider to get payment info
+ │
+ ▼
+ User completes payment
+ ├─ EasyPay → QR code / H5 redirect
+ ├─ Alipay → PC page pay / H5 mobile pay
+ ├─ WeChat Pay → Native QR / H5 pay
+ └─ Stripe → Payment Element (card/Alipay/WeChat/etc.)
+ │
+ ▼
+ Webhook callback verified → Order PAID
+ │
+ ▼
+ Auto top-up to user balance → Order COMPLETED
+```
+
+### Order Status Reference
+
+| Status | Description |
+|--------|-------------|
+| `PENDING` | Waiting for user to complete payment |
+| `PAID` | Payment confirmed, awaiting balance credit |
+| `COMPLETED` | Balance credited successfully |
+| `EXPIRED` | Timed out without payment |
+| `CANCELLED` | Cancelled by user |
+| `FAILED` | Balance credit failed, admin can retry |
+| `REFUND_REQUESTED` | Refund requested |
+| `REFUNDING` | Refund in progress |
+| `REFUNDED` | Refund completed |
+
+### Timeout and Fallback
+
+- Before marking an order as expired, the background job queries the upstream payment status first
+- If the user has actually paid but the callback was delayed, the system will reconcile automatically
+- The background job runs every 60 seconds to check for timed-out orders
+
+---
+
+## Migrating from Sub2ApiPay
+
+If you previously used [Sub2ApiPay](https://github.com/touwaeriol/sub2apipay) as an external payment system, you can migrate to the built-in payment system:
+
+### Key Differences
+
+| Aspect | Sub2ApiPay | Built-in Payment |
+|--------|-----------|-----------------|
+| Deployment | Separate service (Next.js + PostgreSQL) | Built into Sub2API, no extra deployment |
+| Payment Methods | EasyPay, Alipay, WeChat, Stripe | Same |
+| Configuration | Environment variables + separate admin UI | Unified in Sub2API admin dashboard |
+| Top-up Integration | Via Admin API callback | Internal processing, more reliable |
+| Subscription Plans | Supported | Not yet (planned) |
+| Order Management | Separate admin interface | Integrated in Sub2API admin dashboard |
+
+### Migration Steps
+
+1. Enable payment in Sub2API admin dashboard and configure providers (use the same payment credentials)
+2. Update webhook callback URLs to Sub2API's callback endpoints
+3. Verify that new orders are processed correctly via built-in payment
+4. Decommission the Sub2ApiPay service
+
+> **Note**: Historical order data from Sub2ApiPay will not be automatically migrated. Keep Sub2ApiPay running for a while to access historical records.
diff --git a/docs/PAYMENT_CN.md b/docs/PAYMENT_CN.md
new file mode 100644
index 0000000000000000000000000000000000000000..9d96557f68356dbc16a9041483e48b6a744f5fcb
--- /dev/null
+++ b/docs/PAYMENT_CN.md
@@ -0,0 +1,273 @@
+# 支付系统配置指南
+
+Sub2API 内置支付系统,支持用户自助充值,无需部署独立的支付服务。
+
+---
+
+## 目录
+
+- [支持的支付方式](#支持的支付方式)
+- [快速开始](#快速开始)
+- [系统设置](#系统设置)
+- [服务商配置](#服务商配置)
+- [服务商实例管理](#服务商实例管理)
+- [Webhook 配置](#webhook-配置)
+- [支付流程](#支付流程)
+- [从 Sub2ApiPay 迁移](#从-sub2apipay-迁移)
+
+---
+
+## 支持的支付方式
+
+| 服务商 | 支付方式 | 说明 |
+|--------|---------|------|
+| **EasyPay(易支付)** | 支付宝、微信支付 | 兼容易支付协议的第三方聚合支付 |
+| **支付宝官方** | 支付宝 PC 页面支付、H5 手机网站支付 | 直接对接支付宝开放平台,自动根据终端切换 |
+| **微信官方** | Native 扫码支付、H5 支付 | 直接对接微信支付 APIv3,移动端优先 H5 |
+| **Stripe** | 银行卡、支付宝、微信支付、Link 等 | 国际支付,支持多币种 |
+
+> 支付宝官方 / 微信官方与 EasyPay 可以共存。官方渠道直接对接 API,资金直达商户账户,手续费更低;EasyPay 通过第三方平台聚合,接入门槛更低。
+
+> **EasyPay 推荐**:个人推荐 [ZPay](https://z-pay.cn/?uid=23808)(`https://z-pay.cn/?uid=23808`)作为 EasyPay 服务商(链接含 [Sub2ApiPay](https://github.com/touwaeriol/sub2apipay) 原作者 [@touwaeriol](https://github.com/touwaeriol) 的邀请码,介意可去掉)。ZPay 支持**个人用户**(无营业执照)每日 1 万元以内交易;拥有营业执照则无限额。支付渠道的安全性、稳定性及合规性请自行鉴别,本项目不对任何第三方支付服务商做担保或背书。
+
+---
+
+## 快速开始
+
+1. 进入管理后台 → **设置** → **支付设置** 标签页
+2. 开启 **启用支付**
+3. 配置基本参数(金额范围、超时时间等)
+4. 在 **服务商管理** 中添加至少一个服务商实例
+5. 用户即可在前端页面进行充值
+
+---
+
+## 系统设置
+
+在管理后台 **设置 → 支付设置** 中配置以下参数:
+
+### 基本设置
+
+| 设置项 | 说明 | 默认值 |
+|--------|------|--------|
+| **启用支付** | 启用或禁用支付系统 | 关闭 |
+| **商品名前缀** | 支付页面显示的商品名前缀 | - |
+| **商品名后缀** | 商品名后缀(如"元") | - |
+| **最低金额** | 单笔最低充值金额 | 1 |
+| **最高金额** | 单笔最高充值金额(留空表示不限制) | - |
+| **每日限额** | 每用户每日累计充值上限(留空表示不限制) | - |
+| **订单超时时间** | 订单超时分钟数,至少 1 分钟 | 5 |
+| **最大待支付订单数** | 同一用户最大并行待支付订单数 | 3 |
+| **负载均衡策略** | 多服务商实例时的选择策略 | 最少金额 |
+
+### 负载均衡策略
+
+| 策略 | 说明 |
+|------|------|
+| **轮询(round-robin)** | 按顺序轮流分配到各服务商实例 |
+| **最少金额(least-amount)** | 优先分配到当日累计金额最少的实例 |
+
+### 取消频率限制
+
+防止用户频繁创建并取消订单:
+
+| 设置项 | 说明 |
+|--------|------|
+| **启用限制** | 开关 |
+| **窗口模式** | 滚动窗口 / 固定窗口 |
+| **时间窗口** | 窗口长度 |
+| **窗口单位** | 分钟 / 小时 |
+| **最大次数** | 窗口内允许的最大取消次数 |
+
+### 帮助信息
+
+| 设置项 | 说明 |
+|--------|------|
+| **帮助图片** | 充值页面显示的客服二维码等图片(支持上传) |
+| **帮助文本** | 充值页面显示的说明文字 |
+
+---
+
+## 服务商配置
+
+每种服务商需要不同的凭证和参数。在 **服务商管理 → 添加服务商** 中选择类型后填写。
+
+> **回调地址自动生成**:添加服务商时,异步回调地址(Notify URL)和同步跳转地址(Return URL)由系统根据你的站点域名自动拼接,无需手动填写。管理员只需确认域名正确即可。
+
+### EasyPay(易支付)
+
+兼容任何 EasyPay 协议的支付服务商。
+
+| 参数 | 说明 | 必填 |
+|------|------|------|
+| **商户 ID(PID)** | EasyPay 商户 ID | 是 |
+| **商户密钥(PKey)** | EasyPay 商户密钥 | 是 |
+| **API 地址** | EasyPay API 基础地址 | 是 |
+| **支付宝通道 ID** | 指定支付宝通道(可选) | 否 |
+| **微信通道 ID** | 指定微信通道(可选) | 否 |
+
+### 支付宝官方
+
+直接对接支付宝开放平台,支持 PC 页面支付和 H5 手机网站支付。
+
+| 参数 | 说明 | 必填 |
+|------|------|------|
+| **AppID** | 支付宝应用 AppID | 是 |
+| **应用私钥** | RSA2 应用私钥 | 是 |
+| **支付宝公钥** | 支付宝公钥 | 是 |
+
+### 微信官方
+
+直接对接微信支付 APIv3,支持 Native 扫码支付和 H5 支付。
+
+| 参数 | 说明 | 必填 |
+|------|------|------|
+| **AppID** | 微信支付 AppID | 是 |
+| **商户号(MchID)** | 微信支付商户号 | 是 |
+| **商户 API 私钥** | 商户 API 私钥(PEM 格式) | 是 |
+| **APIv3 密钥** | 32 位 APIv3 密钥 | 是 |
+| **微信支付公钥** | 微信支付公钥(PEM 格式) | 是 |
+| **微信支付公钥 ID** | 微信支付公钥 ID | 否 |
+| **商户证书序列号** | 商户证书序列号 | 否 |
+
+### Stripe
+
+国际支付平台,支持多种支付方式和币种。
+
+| 参数 | 说明 | 必填 |
+|------|------|------|
+| **Secret Key** | Stripe 密钥(`sk_live_...` 或 `sk_test_...`) | 是 |
+| **Publishable Key** | Stripe 可公开密钥(`pk_live_...` 或 `pk_test_...`) | 是 |
+| **Webhook Secret** | Stripe Webhook 签名密钥(`whsec_...`) | 是 |
+
+---
+
+## 服务商实例管理
+
+同一种服务商可以创建**多个实例**,实现负载均衡和风控:
+
+- **多实例负载均衡** — 按轮询或最少金额策略分流订单
+- **独立限额** — 每个实例可独立配置单笔最小/最大金额和每日限额
+- **独立启停** — 可单独启用/禁用某个实例,不影响其他实例
+- **退款控制** — 每个实例可单独开启或关闭退款功能
+- **支付方式** — 每个实例可选择支持的支付方式子集
+- **排序** — 拖拽调整实例顺序
+
+### 实例限额配置
+
+每个实例支持以下限额:
+
+| 限额项 | 说明 |
+|--------|------|
+| **单笔最小金额** | 该实例接受的最小订单金额 |
+| **单笔最大金额** | 该实例接受的最大订单金额 |
+| **每日限额** | 该实例每日累计交易上限 |
+
+> 负载均衡时,系统会自动跳过超出限额的实例。
+
+---
+
+## Webhook 配置
+
+支付回调是支付系统的核心环节,必须正确配置:
+
+### 回调地址格式
+
+添加服务商时,系统会自动根据站点域名拼接回调地址,格式如下:
+
+| 服务商 | 回调路径 |
+|--------|---------|
+| **EasyPay** | `https://your-domain.com/api/v1/payment/webhook/easypay` |
+| **支付宝官方** | `https://your-domain.com/api/v1/payment/webhook/alipay` |
+| **微信官方** | `https://your-domain.com/api/v1/payment/webhook/wxpay` |
+| **Stripe** | `https://your-domain.com/api/v1/payment/webhook/stripe` |
+
+> 将 `your-domain.com` 替换为你的实际域名。EasyPay / 支付宝 / 微信的回调地址在添加服务商时自动填入,无需手动配置。
+
+### Stripe Webhook 设置
+
+1. 登录 [Stripe Dashboard](https://dashboard.stripe.com/)
+2. 进入 **Developers → Webhooks**
+3. 添加端点,填写回调地址
+4. 订阅事件:`payment_intent.succeeded`、`payment_intent.payment_failed`
+5. 将生成的 Webhook Secret(`whsec_...`)填入服务商配置
+
+### 注意事项
+
+- 回调地址必须是 **HTTPS**(Stripe 强制要求,其他服务商强烈推荐)
+- 确保服务器防火墙允许支付平台的回调请求
+- 系统会自动进行签名验证,防止伪造回调
+- 支付成功后自动完成余额充值,无需人工干预
+
+---
+
+## 支付流程
+
+```
+用户选择充值金额和支付方式
+ │
+ ▼
+ 创建订单 (PENDING)
+ ├─ 校验金额范围、待支付订单数、每日限额
+ ├─ 负载均衡选择服务商实例
+ └─ 调用服务商获取支付信息
+ │
+ ▼
+ 用户完成支付
+ ├─ EasyPay → 扫码 / H5 跳转
+ ├─ 支付宝官方 → PC 页面支付 / H5 手机网站支付
+ ├─ 微信官方 → Native 扫码 / H5 支付
+ └─ Stripe → Payment Element(银行卡/支付宝/微信等)
+ │
+ ▼
+ 支付回调验签 → 订单 PAID
+ │
+ ▼
+ 自动充值到用户余额 → 订单 COMPLETED
+```
+
+### 订单状态说明
+
+| 状态 | 说明 |
+|------|------|
+| `PENDING` | 待支付,等待用户完成支付 |
+| `PAID` | 已支付,等待充值到账 |
+| `COMPLETED` | 已完成,余额已到账 |
+| `EXPIRED` | 已过期,超时未支付 |
+| `CANCELLED` | 已取消,用户主动取消 |
+| `FAILED` | 充值失败,可管理员重试 |
+| `REFUND_REQUESTED` | 已申请退款 |
+| `REFUNDING` | 退款处理中 |
+| `REFUNDED` | 已退款 |
+
+### 超时与兜底
+
+- 订单超时后,后台任务会先查询上游支付状态再标记过期
+- 如果用户实际已支付但回调延迟,系统会通过查询补单
+- 后台任务每 60 秒执行一次超时检查
+
+---
+
+## 从 Sub2ApiPay 迁移
+
+如果你之前使用 [Sub2ApiPay](https://github.com/touwaeriol/sub2apipay) 作为外部支付系统,现在可以迁移到内置支付:
+
+### 主要差异
+
+| 对比项 | Sub2ApiPay | 内置支付 |
+|--------|-----------|---------|
+| 部署方式 | 独立服务(Next.js + PostgreSQL) | 内置于 Sub2API,无需额外部署 |
+| 支付方式 | EasyPay、支付宝、微信、Stripe | 相同 |
+| 配置方式 | 环境变量 + 独立管理后台 | Sub2API 管理后台内统一配置 |
+| 充值对接 | 通过 Admin API 回调 | 内部直接处理,更可靠 |
+| 订阅套餐 | 支持 | 暂不支持(计划中) |
+| 订单管理 | 独立管理界面 | 集成在 Sub2API 管理后台 |
+
+### 迁移步骤
+
+1. 在 Sub2API 管理后台启用支付并配置服务商(使用相同的支付凭证)
+2. 更新 Webhook 回调地址为 Sub2API 的回调地址
+3. 确认新订单通过内置支付正常处理
+4. 停用 Sub2ApiPay 服务
+
+> **注意**:Sub2ApiPay 中的历史订单数据不会自动迁移。建议保留 Sub2ApiPay 一段时间以便查询历史记录。
diff --git a/frontend/package.json b/frontend/package.json
index d2a6deded711d97decc0f73b42ae1429600fe871..a220d3a7fdd693a920af974f1a71765e86097d15 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -16,9 +16,10 @@
},
"dependencies": {
"@lobehub/icons": "^4.0.2",
+ "@stripe/stripe-js": "^9.0.1",
"@tanstack/vue-virtual": "^3.13.23",
"@vueuse/core": "^10.7.0",
- "axios": "^1.13.5",
+ "axios": "^1.15.0",
"chart.js": "^4.4.1",
"dompurify": "^3.3.1",
"driver.js": "^1.4.0",
diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml
index 505b72f388765694056841ece820078c485e4879..0a7b3fa1cbe276307d45f1c43a64d32ff7a75e3c 100644
--- a/frontend/pnpm-lock.yaml
+++ b/frontend/pnpm-lock.yaml
@@ -11,6 +11,9 @@ importers:
'@lobehub/icons':
specifier: ^4.0.2
version: 4.0.2(@lobehub/ui@4.9.2)(@types/react@19.2.7)(antd@6.1.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@stripe/stripe-js':
+ specifier: ^9.0.1
+ version: 9.0.1
'@tanstack/vue-virtual':
specifier: ^3.13.23
version: 3.13.23(vue@3.5.26(typescript@5.6.3))
@@ -18,8 +21,8 @@ importers:
specifier: ^10.7.0
version: 10.11.1(vue@3.5.26(typescript@5.6.3))
axios:
- specifier: ^1.13.5
- version: 1.13.5
+ specifier: ^1.15.0
+ version: 1.15.0
chart.js:
specifier: ^4.4.1
version: 4.5.1
@@ -134,11 +137,11 @@ packages:
resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
engines: {node: '>=6.0.0'}
- '@ant-design/colors@8.0.0':
- resolution: {integrity: sha512-6YzkKCw30EI/E9kHOIXsQDHmMvTllT8STzjMb4K2qzit33RW2pqCJP0sk+hidBntXxE+Vz4n1+RvCTfBw6OErw==}
+ '@ant-design/colors@8.0.1':
+ resolution: {integrity: sha512-foPVl0+SWIslGUtD/xBr1p9U4AKzPhNYEseXYRRo5QSzGACYZrQbe11AYJbYfAWnWSpGBx6JjBmSeugUsD9vqQ==}
- '@ant-design/cssinjs-utils@2.0.2':
- resolution: {integrity: sha512-Mq3Hm6fJuQeFNKSp3+yT4bjuhVbdrsyXE2RyfpJFL0xiYNZdaJ6oFaE3zFrzmHbmvTd2Wp3HCbRtkD4fU+v2ZA==}
+ '@ant-design/cssinjs-utils@2.1.2':
+ resolution: {integrity: sha512-5fTHQ158jJJ5dC/ECeyIdZUzKxE/mpEMRZxthyG1sw/AKRHKgJBg00Yi6ACVXgycdje7KahRNvNET/uBccwCnA==}
peerDependencies:
react: '>=18'
react-dom: '>=18'
@@ -149,15 +152,21 @@ packages:
react: '>=16.0.0'
react-dom: '>=16.0.0'
- '@ant-design/fast-color@3.0.0':
- resolution: {integrity: sha512-eqvpP7xEDm2S7dUzl5srEQCBTXZMmY3ekf97zI+M2DHOYyKdJGH0qua0JACHTqbkRnD/KHFQP9J1uMJ/XWVzzA==}
+ '@ant-design/cssinjs@2.1.2':
+ resolution: {integrity: sha512-2Hy8BnCEH31xPeSLbhhB2ctCPXE2ZnASdi+KbSeS79BNbUhL9hAEe20SkUk+BR8aKTmqb6+FKFruk7w8z0VoRQ==}
+ peerDependencies:
+ react: '>=16.0.0'
+ react-dom: '>=16.0.0'
+
+ '@ant-design/fast-color@3.0.1':
+ resolution: {integrity: sha512-esKJegpW4nckh0o6kV3Tkb7NPIZYbPnnFxmQDUmL08ukXZAvV85TZBr70eGuke/CIArLaP6aw8lt9KILjnWuOw==}
engines: {node: '>=8.x'}
'@ant-design/icons-svg@4.4.2':
resolution: {integrity: sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==}
- '@ant-design/icons@6.1.0':
- resolution: {integrity: sha512-KrWMu1fIg3w/1F2zfn+JlfNDU8dDqILfA5Tg85iqs1lf8ooyGlbkA+TkwfOKKgqpUmAiRY1PTFpuOU2DAIgSUg==}
+ '@ant-design/icons@6.1.1':
+ resolution: {integrity: sha512-AMT4N2y++TZETNHiM77fs4a0uPVCJGuL5MTonk13Pvv7UN7sID1cNEZOc1qNqx6zLKAOilTEFAdAoAFKa0U//Q==}
engines: {node: '>=8'}
peerDependencies:
react: '>=16.0.0'
@@ -208,6 +217,10 @@ packages:
resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==}
engines: {node: '>=6.9.0'}
+ '@babel/runtime@7.29.2':
+ resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==}
+ engines: {node: '>=6.9.0'}
+
'@babel/template@7.27.2':
resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==}
engines: {node: '>=6.9.0'}
@@ -220,8 +233,8 @@ packages:
resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==}
engines: {node: '>=6.9.0'}
- '@base-ui/react@1.0.0':
- resolution: {integrity: sha512-4USBWz++DUSLTuIYpbYkSgy1F9ZmNG9S/lXvlUN6qMK0P0RlW+6eQmDUB4DgZ7HVvtXl4pvi4z5J2fv6Z3+9hg==}
+ '@base-ui/react@1.3.0':
+ resolution: {integrity: sha512-FwpKqZbPz14AITp1CVgf4AjhKPe1OeeVKSBMdgD10zbFlj3QSWelmtCMLi2+/PFZZcIm3l87G7rwtCZJwHyXWA==}
engines: {node: '>=14.0.0'}
peerDependencies:
'@types/react': ^17 || ^18 || ^19
@@ -231,8 +244,8 @@ packages:
'@types/react':
optional: true
- '@base-ui/utils@0.2.3':
- resolution: {integrity: sha512-/CguQ2PDaOzeVOkllQR8nocJ0FFIDqsWIcURsVmm53QGo8NhFNpePjNlyPIB41luxfOqnG7PU0xicMEw3ls7XQ==}
+ '@base-ui/utils@0.2.6':
+ resolution: {integrity: sha512-yQ+qeuqohwhsNpoYDqqXaLllYAkPCP4vYdDrVo8FQXaAPfHWm1pG/Vm+jmGTA5JFS0BAIjookyapuJFY8F9PIw==}
peerDependencies:
'@types/react': ^17 || ^18 || ^19
react: ^17 || ^18 || ^19
@@ -244,23 +257,23 @@ packages:
'@bcoe/v8-coverage@0.2.3':
resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
- '@braintree/sanitize-url@7.1.1':
- resolution: {integrity: sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw==}
+ '@braintree/sanitize-url@7.1.2':
+ resolution: {integrity: sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==}
- '@chevrotain/cst-dts-gen@11.0.3':
- resolution: {integrity: sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==}
+ '@chevrotain/cst-dts-gen@12.0.0':
+ resolution: {integrity: sha512-fSL4KXjTl7cDgf0B5Rip9Q05BOrYvkJV/RrBTE/bKDN096E4hN/ySpcBK5B24T76dlQ2i32Zc3PAE27jFnFrKg==}
- '@chevrotain/gast@11.0.3':
- resolution: {integrity: sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==}
+ '@chevrotain/gast@12.0.0':
+ resolution: {integrity: sha512-1ne/m3XsIT8aEdrvT33so0GUC+wkctpUPK6zU9IlOyJLUbR0rg4G7ZiApiJbggpgPir9ERy3FRjT6T7lpgetnQ==}
- '@chevrotain/regexp-to-ast@11.0.3':
- resolution: {integrity: sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==}
+ '@chevrotain/regexp-to-ast@12.0.0':
+ resolution: {integrity: sha512-p+EW9MaJwgaHguhoqwOtx/FwuGr+DnNn857sXWOi/mClXIkPGl3rn7hGNWvo31HA3vyeQxjqe+H36yZJwYU8cA==}
- '@chevrotain/types@11.0.3':
- resolution: {integrity: sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==}
+ '@chevrotain/types@12.0.0':
+ resolution: {integrity: sha512-S+04vjFQKeuYw0/eW3U52LkAHQsB1ASxsPGsLPUyQgrZ2iNNibQrsidruDzjEX2JYfespXMG0eZmXlhA6z7nWA==}
- '@chevrotain/utils@11.0.3':
- resolution: {integrity: sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==}
+ '@chevrotain/utils@12.0.0':
+ resolution: {integrity: sha512-lB59uJoaGIfOOL9knQqQRfhl9g7x8/wqFkp13zTdkRu1huG9kg6IJs1O8hqj9rs6h7orGxHJUKb+mX3rPbWGhA==}
'@csstools/color-helpers@5.1.0':
resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==}
@@ -536,26 +549,26 @@ packages:
resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
- '@floating-ui/core@1.7.3':
- resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==}
+ '@floating-ui/core@1.7.5':
+ resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==}
- '@floating-ui/dom@1.7.4':
- resolution: {integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==}
+ '@floating-ui/dom@1.7.6':
+ resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==}
- '@floating-ui/react-dom@2.1.6':
- resolution: {integrity: sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==}
+ '@floating-ui/react-dom@2.1.8':
+ resolution: {integrity: sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==}
peerDependencies:
react: '>=16.8.0'
react-dom: '>=16.8.0'
- '@floating-ui/react@0.27.16':
- resolution: {integrity: sha512-9O8N4SeG2z++TSM8QA/KTeKFBVCNEz/AGS7gWPJf6KFRzmRWixFRnCnkPHRDwSVZW6QPDO6uT0P2SpWNKCc9/g==}
+ '@floating-ui/react@0.27.19':
+ resolution: {integrity: sha512-31B8h5mm8YxotlE7/AU/PhNAl8eWxAmjL/v2QOxroDNkTFLk3Uu82u63N3b6TXa4EGJeeZLVcd/9AlNlVqzeog==}
peerDependencies:
react: '>=17.0.0'
react-dom: '>=17.0.0'
- '@floating-ui/utils@0.2.10':
- resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==}
+ '@floating-ui/utils@0.2.11':
+ resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==}
'@giscus/react@3.1.0':
resolution: {integrity: sha512-0TCO2TvL43+oOdyVVGHDItwxD1UMKP2ZYpT6gXmhFOqfAJtZxTzJ9hkn34iAF/b6YzyJ4Um89QIt9z/ajmAEeg==}
@@ -618,8 +631,8 @@ packages:
'@kurkle/color@0.3.4':
resolution: {integrity: sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==}
- '@lit-labs/ssr-dom-shim@1.5.0':
- resolution: {integrity: sha512-HLomZXMmrCFHSRKESF5vklAKsDY7/fsT/ZhqCu3V0UoW/Qbv8wxmO4W9bx4KnCCF2Zak4yuk+AGraK/bPmI4kA==}
+ '@lit-labs/ssr-dom-shim@1.5.1':
+ resolution: {integrity: sha512-Aou5UdlSpr5whQe8AA/bZG0jMj96CoJIWbGfZ91qieWu5AWUMKw8VR/pAkQkJYvBNhmCcWnZlyyk5oze8JIqYA==}
'@lit/reactive-element@2.1.2':
resolution: {integrity: sha512-pbCDiVMnne1lYUIaYNN5wrwQXDtHaYtg7YEFPeW+hws6U47WeFvISGUWekPGKWOP1ygrs0ef0o1VJMk1exos5A==}
@@ -660,8 +673,8 @@ packages:
'@types/react': '>=16'
react: '>=16'
- '@mermaid-js/parser@0.6.3':
- resolution: {integrity: sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA==}
+ '@mermaid-js/parser@1.1.0':
+ resolution: {integrity: sha512-gxK9ZX2+Fex5zu8LhRQoMeMPEHbc73UKZ0FQ54YrQtUxE1VVhMwzeNtKRPAu5aXks4FasbMe4xB4bWrmq6Jlxw==}
'@nodelib/fs.scandir@2.1.5':
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
@@ -682,8 +695,8 @@ packages:
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'}
- '@primer/octicons@19.21.1':
- resolution: {integrity: sha512-7tgtBkCNcg75YJnckinzvES+uxysYQCe+CHSEnzr3VYgxttzKRvfmrnVogl3aEuHCQP4xhiE9k2lFDhYwGtTzQ==}
+ '@primer/octicons@19.23.1':
+ resolution: {integrity: sha512-CzjGmxkmNhyst6EekrS3SJPdtzgIkUMP/LSJch65y99/kmiFXbO1a+q7zoYe3hnI9NaOM0IN+ydDIbOmd8YqcA==}
'@radix-ui/primitive@1.1.3':
resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==}
@@ -929,8 +942,8 @@ packages:
'@radix-ui/rect@1.1.1':
resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==}
- '@rc-component/async-validator@5.0.4':
- resolution: {integrity: sha512-qgGdcVIF604M9EqjNF0hbUTz42bz/RDtxWdWuU5EQe3hi7M8ob54B6B35rOsvX5eSvIHIzT9iH1R3n+hk3CGfg==}
+ '@rc-component/async-validator@5.1.0':
+ resolution: {integrity: sha512-n4HcR5siNUXRX23nDizbZBQPO0ZM/5oTtmKZ6/eqL0L2bo747cklFdZGRN2f+c9qWGICwDzrhW0H7tE9PptdcA==}
engines: {node: '>=14.x'}
'@rc-component/cascader@1.10.0':
@@ -981,8 +994,8 @@ packages:
react: '>=16.11.0'
react-dom: '>=16.11.0'
- '@rc-component/form@1.6.0':
- resolution: {integrity: sha512-A7vrN8kExtw4sW06mrsgCb1rowhvBFFvQU6Bk/NL0Fj6Wet/5GF0QnGCxBu/sG3JI9FEhsJWES0D44BW2d0hzg==}
+ '@rc-component/form@1.6.2':
+ resolution: {integrity: sha512-OgIn2RAoaSBqaIgzJf/X6iflIa9LpTozci1lagLBdURDFhGA370v0+T0tXxOi8YShMjTha531sFhwtnrv+EJaQ==}
engines: {node: '>=8.x'}
peerDependencies:
react: '>=16.9.0'
@@ -1018,8 +1031,8 @@ packages:
react: '>=16.9.0'
react-dom: '>=16.9.0'
- '@rc-component/mini-decimal@1.1.0':
- resolution: {integrity: sha512-jS4E7T9Li2GuYwI6PyiVXmxTiM6b07rlD9Ge8uGZSCz3WlzcG5ZK7g5bbuKNeZ9pgUuPK/5guV781ujdVpm4HQ==}
+ '@rc-component/mini-decimal@1.1.3':
+ resolution: {integrity: sha512-bk/FJ09fLf+NLODMAFll6CfYrHPBioTedhW6lxDBuuWucJEqFUd4l/D/5JgIi3dina6sYahB8iuPAZTNz2pMxw==}
engines: {node: '>=8.x'}
'@rc-component/motion@1.1.6':
@@ -1054,8 +1067,8 @@ packages:
react: '>=16.9.0'
react-dom: '>=16.9.0'
- '@rc-component/picker@1.9.0':
- resolution: {integrity: sha512-OLisdk8AWVCG9goBU1dWzuH5QlBQk8jktmQ6p0/IyBFwdKGwyIZOSjnBYo8hooHiTdl0lU+wGf/OfMtVBw02KQ==}
+ '@rc-component/picker@1.9.1':
+ resolution: {integrity: sha512-9FBYYsvH3HMLICaPDA/1Th5FLaDkFa7qAtangIdlhKb3ZALaR745e9PsOhheJb6asS4QXc12ffiAcjdkZ4C5/g==}
engines: {node: '>=12.x'}
peerDependencies:
date-fns: '>= 2.x'
@@ -1108,8 +1121,8 @@ packages:
react: '>=16.9.0'
react-dom: '>=16.9.0'
- '@rc-component/resize-observer@1.0.1':
- resolution: {integrity: sha512-r+w+Mz1EiueGk1IgjB3ptNXLYSLZ5vnEfKHH+gfgj7JMupftyzvUUl3fRcMZe5uMM04x0n8+G2o/c6nlO2+Wag==}
+ '@rc-component/resize-observer@1.1.2':
+ resolution: {integrity: sha512-t/Bb0W8uvL4PYKAB3YcChC+DlHh0Wt5kM7q/J+0qpVEUMLe7Hk5zuvc9km0hMnTFPSx5Z7Wu/fzCLN6erVLE8Q==}
peerDependencies:
react: '>=16.9.0'
react-dom: '>=16.9.0'
@@ -1193,15 +1206,15 @@ packages:
react: '*'
react-dom: '*'
- '@rc-component/trigger@2.3.0':
- resolution: {integrity: sha512-iwaxZyzOuK0D7lS+0AQEtW52zUWxoGqTGkke3dRyb8pYiShmRpCjB/8TzPI4R6YySCH7Vm9BZj/31VPiiQTLBg==}
+ '@rc-component/trigger@2.3.1':
+ resolution: {integrity: sha512-ORENF39PeXTzM+gQEshuk460Z8N4+6DkjpxlpE7Q3gYy1iBpLrx0FOJz3h62ryrJZ/3zCAUIkT1Pb/8hHWpb3A==}
engines: {node: '>=8.x'}
peerDependencies:
react: '>=16.9.0'
react-dom: '>=16.9.0'
- '@rc-component/trigger@3.8.1':
- resolution: {integrity: sha512-walnDJnKq+OcPQFHBMN+YZmdHV8+6z75+Rgpc0dW1c+Dmy6O7tRueDs4LdbwjlryQfTdsw84PIkNPzcx5yQ7qQ==}
+ '@rc-component/trigger@3.9.0':
+ resolution: {integrity: sha512-X8btpwfrT27AgrZVOz4swclhEHTZcqaHeQMXXBgveagOiakTa36uObXbdwerXffgV8G9dH1fAAE0DHtVQs8EHg==}
engines: {node: '>=8.x'}
peerDependencies:
react: '>=18.0.0'
@@ -1213,6 +1226,12 @@ packages:
react: '>=16.9.0'
react-dom: '>=16.9.0'
+ '@rc-component/util@1.10.1':
+ resolution: {integrity: sha512-q++9S6rUa5Idb/xIBNz6jtvumw5+O5YV5V0g4iK9mn9jWs4oGJheE3ZN1kAnE723AXyaD8v95yeOASmdk8Jnng==}
+ peerDependencies:
+ react: '>=18.0.0'
+ react-dom: '>=18.0.0'
+
'@rc-component/util@1.7.0':
resolution: {integrity: sha512-tIvIGj4Vl6fsZFvWSkYw9sAfiCKUXMyhVz6kpKyZbwyZyRPqv2vxYZROdaO1VB4gqTNvUZFXh6i3APUiterw5g==}
peerDependencies:
@@ -1347,26 +1366,26 @@ packages:
cpu: [x64]
os: [win32]
- '@shikijs/core@3.20.0':
- resolution: {integrity: sha512-f2ED7HYV4JEk827mtMDwe/yQ25pRiXZmtHjWF8uzZKuKiEsJR7Ce1nuQ+HhV9FzDcbIo4ObBCD9GPTzNuy9S1g==}
+ '@shikijs/core@3.23.0':
+ resolution: {integrity: sha512-NSWQz0riNb67xthdm5br6lAkvpDJRTgB36fxlo37ZzM2yq0PQFFzbd8psqC2XMPgCzo1fW6cVi18+ArJ44wqgA==}
- '@shikijs/engine-javascript@3.20.0':
- resolution: {integrity: sha512-OFx8fHAZuk7I42Z9YAdZ95To6jDePQ9Rnfbw9uSRTSbBhYBp1kEOKv/3jOimcj3VRUKusDYM6DswLauwfhboLg==}
+ '@shikijs/engine-javascript@3.23.0':
+ resolution: {integrity: sha512-aHt9eiGFobmWR5uqJUViySI1bHMqrAgamWE1TYSUoftkAeCCAiGawPMwM+VCadylQtF4V3VNOZ5LmfItH5f3yA==}
- '@shikijs/engine-oniguruma@3.20.0':
- resolution: {integrity: sha512-Yx3gy7xLzM0ZOjqoxciHjA7dAt5tyzJE3L4uQoM83agahy+PlW244XJSrmJRSBvGYELDhYXPacD4R/cauV5bzQ==}
+ '@shikijs/engine-oniguruma@3.23.0':
+ resolution: {integrity: sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g==}
- '@shikijs/langs@3.20.0':
- resolution: {integrity: sha512-le+bssCxcSHrygCWuOrYJHvjus6zhQ2K7q/0mgjiffRbkhM4o1EWu2m+29l0yEsHDbWaWPNnDUTRVVBvBBeKaA==}
+ '@shikijs/langs@3.23.0':
+ resolution: {integrity: sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg==}
- '@shikijs/themes@3.20.0':
- resolution: {integrity: sha512-U1NSU7Sl26Q7ErRvJUouArxfM2euWqq1xaSrbqMu2iqa+tSp0D1Yah8216sDYbdDHw4C8b75UpE65eWorm2erQ==}
+ '@shikijs/themes@3.23.0':
+ resolution: {integrity: sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA==}
- '@shikijs/transformers@3.20.0':
- resolution: {integrity: sha512-PrHHMRr3Q5W1qB/42kJW6laqFyWdhrPF2hNR9qjOm1xcSiAO3hAHo7HaVyHE6pMyevmy3i51O8kuGGXC78uK3g==}
+ '@shikijs/transformers@3.23.0':
+ resolution: {integrity: sha512-F9msZVxdF+krQNSdQ4V+Ja5QemeAoTQ2jxt7nJCwhDsdF1JWS3KxIQXA3lQbyKwS3J61oHRUSv4jYWv3CkaKTQ==}
- '@shikijs/types@3.20.0':
- resolution: {integrity: sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw==}
+ '@shikijs/types@3.23.0':
+ resolution: {integrity: sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ==}
'@shikijs/vscode-textmate@10.0.2':
resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==}
@@ -1379,6 +1398,10 @@ packages:
peerDependencies:
react: '>= 16.3.0'
+ '@stripe/stripe-js@9.0.1':
+ resolution: {integrity: sha512-un0URSosrW7wNr7xZ5iI2mC9mdeXZ3KERoVlA2RdmeLXYxHUPXq0yHzir2n/MtyXXEdSaELtz4WXGS6dzPEeKA==}
+ engines: {node: '>=12.16'}
+
'@tanstack/virtual-core@3.13.23':
resolution: {integrity: sha512-zSz2Z2HNyLjCplANTDyl3BcdQJc2k1+yyFoKhNRmCr7V7dY8o8q5m8uFTI1/Pg1kL+Hgrz6u3Xo6eFUB7l66cg==}
@@ -1459,8 +1482,8 @@ packages:
'@types/d3-selection@3.0.11':
resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==}
- '@types/d3-shape@3.1.7':
- resolution: {integrity: sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==}
+ '@types/d3-shape@3.1.8':
+ resolution: {integrity: sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==}
'@types/d3-time-format@4.0.3':
resolution: {integrity: sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==}
@@ -1480,8 +1503,8 @@ packages:
'@types/d3@7.4.3':
resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==}
- '@types/debug@4.1.12':
- resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
+ '@types/debug@4.1.13':
+ resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==}
'@types/dompurify@3.2.0':
resolution: {integrity: sha512-Fgg31wv9QbLDA0SpTOXO3MaxySc4DKGLi8sna4/Utjo4r3ZRPdCt4UQee8BWr+Q5z21yifghREPJGYaEOEIACg==}
@@ -1505,8 +1528,8 @@ packages:
'@types/js-cookie@3.0.6':
resolution: {integrity: sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==}
- '@types/katex@0.16.7':
- resolution: {integrity: sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==}
+ '@types/katex@0.16.8':
+ resolution: {integrity: sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==}
'@types/mdast@4.0.4':
resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==}
@@ -1605,6 +1628,9 @@ packages:
'@ungap/structured-clone@1.3.0':
resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
+ '@upsetjs/venn.js@2.0.0':
+ resolution: {integrity: sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==}
+
'@use-gesture/core@10.3.1':
resolution: {integrity: sha512-WcINiDt8WjqBdUXye25anHiNxPc0VOrlT8F6LLkU6cycrOGUDyY/yyFmsg3k8i5OLvv25llc0QC45GhR/C8llw==}
@@ -1736,6 +1762,11 @@ packages:
engines: {node: '>=0.4.0'}
hasBin: true
+ acorn@8.16.0:
+ resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==}
+ engines: {node: '>=0.4.0'}
+ hasBin: true
+
adler-32@1.3.1:
resolution: {integrity: sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==}
engines: {node: '>=0.8'}
@@ -1744,8 +1775,8 @@ packages:
resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
engines: {node: '>= 14'}
- ahooks@3.9.6:
- resolution: {integrity: sha512-Mr7f05swd5SmKlR9SZo5U6M0LsL4ErweLzpdgXjA1JPmnZ78Vr6wzx0jUtvoxrcqGKYnX0Yjc02iEASVxHFPjQ==}
+ ahooks@3.9.7:
+ resolution: {integrity: sha512-S0lvzhbdlhK36RFBkGv+RbOM/dbbweym+BIHM/bwwuWVSVN5TuVErHPMWo4w0t1NDYg5KPp2iEf7Y7E5LASYiw==}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
@@ -1827,8 +1858,8 @@ packages:
peerDependencies:
postcss: ^8.1.0
- axios@1.13.5:
- resolution: {integrity: sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==}
+ axios@1.15.0:
+ resolution: {integrity: sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==}
babel-plugin-macros@3.1.0:
resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==}
@@ -1924,13 +1955,14 @@ packages:
resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==}
engines: {node: '>= 16'}
- chevrotain-allstar@0.3.1:
- resolution: {integrity: sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==}
+ chevrotain-allstar@0.4.1:
+ resolution: {integrity: sha512-PvVJm3oGqrveUVW2Vt/eZGeiAIsJszYweUcYwcskg9e+IubNYKKD+rHHem7A6XVO22eDAL+inxNIGAzZ/VIWlA==}
peerDependencies:
- chevrotain: ^11.0.0
+ chevrotain: ^12.0.0
- chevrotain@11.0.3:
- resolution: {integrity: sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==}
+ chevrotain@12.0.0:
+ resolution: {integrity: sha512-csJvb+6kEiQaqo1woTdSAuOWdN0WTLIydkKrBnS+V5gZz0oqBrp4kQ35519QgK6TpBThiG3V1vNSHlIkv4AglQ==}
+ engines: {node: '>=22.0.0'}
chokidar@3.6.0:
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
@@ -1952,10 +1984,6 @@ packages:
cliui@6.0.0:
resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==}
- clsx@1.2.1:
- resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==}
- engines: {node: '>=6'}
-
clsx@2.1.1:
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
engines: {node: '>=6'}
@@ -2056,8 +2084,8 @@ packages:
peerDependencies:
cytoscape: ^3.2.0
- cytoscape@3.33.1:
- resolution: {integrity: sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==}
+ cytoscape@3.33.2:
+ resolution: {integrity: sha512-sj4HXd3DokGhzZAdjDejGvTPLqlt84vNFN8m7bGsOzDY5DyVcxIb2ejIXat2Iy7HxWhdT/N1oKyheJ5YdpsGuw==}
engines: {node: '>=0.10'}
d3-array@2.12.1:
@@ -2116,8 +2144,8 @@ packages:
resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==}
engines: {node: '>=12'}
- d3-format@3.1.0:
- resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==}
+ d3-format@3.1.2:
+ resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==}
engines: {node: '>=12'}
d3-geo@3.1.1:
@@ -2199,15 +2227,15 @@ packages:
resolution: {integrity: sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==}
engines: {node: '>=12'}
- dagre-d3-es@7.0.13:
- resolution: {integrity: sha512-efEhnxpSuwpYOKRm/L5KbqoZmNNukHa/Flty4Wp62JRvgH2ojwVgPgdYyr4twpieZnyRDdIH7PY2mopX26+j2Q==}
+ dagre-d3-es@7.0.14:
+ resolution: {integrity: sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==}
data-urls@5.0.0:
resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==}
engines: {node: '>=18'}
- dayjs@1.11.19:
- resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==}
+ dayjs@1.11.20:
+ resolution: {integrity: sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==}
de-indent@1.0.2:
resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==}
@@ -2228,8 +2256,8 @@ packages:
decimal.js@10.6.0:
resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==}
- decode-named-character-reference@1.2.0:
- resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==}
+ decode-named-character-reference@1.3.0:
+ resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==}
decode-uri-component@0.4.1:
resolution: {integrity: sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ==}
@@ -2242,8 +2270,8 @@ packages:
deep-is@0.1.4:
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
- delaunator@5.0.1:
- resolution: {integrity: sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==}
+ delaunator@5.1.0:
+ resolution: {integrity: sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==}
delayed-stream@1.0.0:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
@@ -2276,6 +2304,9 @@ packages:
dompurify@3.3.1:
resolution: {integrity: sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==}
+ dompurify@3.3.3:
+ resolution: {integrity: sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA==}
+
driver.js@1.4.0:
resolution: {integrity: sha512-Gm64jm6PmcU+si21sQhBrTAM1JvUrR0QhNmjkprNLxohOBzul9+pNHXgQaT9lW84gwg9GMLB3NZGuGolsz5uew==}
@@ -2336,8 +2367,8 @@ packages:
resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
engines: {node: '>= 0.4'}
- es-toolkit@1.43.0:
- resolution: {integrity: sha512-SKCT8AsWvYzBBuUqMk4NPwFlSdqLpJwmy6AP322ERn8W2YLIB6JBXnwMI2Qsh2gfphT3q7EKAxKb23cvFHFwKA==}
+ es-toolkit@1.45.1:
+ resolution: {integrity: sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw==}
esast-util-from-estree@2.0.0:
resolution: {integrity: sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==}
@@ -2531,8 +2562,8 @@ packages:
fraction.js@5.3.4:
resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==}
- framer-motion@12.23.26:
- resolution: {integrity: sha512-cPcIhgR42xBn1Uj+PzOyheMtZ73H927+uWPDVhUMqxy8UHt6Okavb6xIz9J/phFUHUj0OncR6UvMfJTXoc/LKA==}
+ framer-motion@12.38.0:
+ resolution: {integrity: sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g==}
peerDependencies:
'@emotion/is-prop-valid': '*'
react: ^18.0.0 || ^19.0.0
@@ -2560,8 +2591,8 @@ packages:
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
engines: {node: 6.* || 8.* || >= 10.*}
- get-east-asian-width@1.4.0:
- resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==}
+ get-east-asian-width@1.5.0:
+ resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==}
engines: {node: '>=18'}
get-intrinsic@1.3.0:
@@ -2707,8 +2738,8 @@ packages:
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
engines: {node: '>= 4'}
- immer@11.1.3:
- resolution: {integrity: sha512-6jQTc5z0KJFtr1UgFpIL3N9XSC3saRaI9PwWtzM2pSqkNGtiNkYY2OSwkOGDK2XcTRcLb1pi/aNkKZz0nxVH4Q==}
+ immer@11.1.4:
+ resolution: {integrity: sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw==}
import-fresh@3.3.1:
resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
@@ -2882,8 +2913,8 @@ packages:
json2mq@0.2.0:
resolution: {integrity: sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==}
- katex@0.16.27:
- resolution: {integrity: sha512-aeQoDkuRWSqQN6nSvVCEFvfXdqo1OQiCmmW1kc9xSdjutPv7BGO7pqY9sQRJpMOGrEdfDgF2TfRXe5eUAD2Waw==}
+ katex@0.16.45:
+ resolution: {integrity: sha512-pQpZbdBu7wCTmQUh7ufPmLr0pFoObnGUoL/yhtwJDgmmQpbkg/0HSVti25Fu4rmd1oCR6NGWe9vqTWuWv3GcNA==}
hasBin: true
keyv@4.5.4:
@@ -2892,9 +2923,9 @@ packages:
khroma@2.1.0:
resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==}
- langium@3.3.1:
- resolution: {integrity: sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==}
- engines: {node: '>=16.0.0'}
+ langium@4.2.2:
+ resolution: {integrity: sha512-JUshTRAfHI4/MF9dH2WupvjSXyn8JBuUEWazB8ZVJUtXutT0doDlAv1XKbZ1Pb5sMexa8FF4CFBc0iiul7gbUQ==}
+ engines: {node: '>=20.10.0', npm: '>=10.2.3'}
layout-base@1.0.2:
resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==}
@@ -2936,11 +2967,8 @@ packages:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
engines: {node: '>=10'}
- lodash-es@4.17.21:
- resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
-
- lodash-es@4.17.22:
- resolution: {integrity: sha512-XEawp1t0gxSi9x01glktRZ5HDy0HXqrM0x5pXQM98EaI0NxO6jVM7omDOxsuEo5UIASAnm2bRp1Jt/e0a2XU8Q==}
+ lodash-es@4.18.1:
+ resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==}
lodash.merge@4.6.2:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
@@ -2948,6 +2976,9 @@ packages:
lodash@4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
+ lodash@4.18.1:
+ resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==}
+
longest-streak@3.1.0:
resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==}
@@ -2998,6 +3029,11 @@ packages:
engines: {node: '>= 20'}
hasBin: true
+ marked@17.0.6:
+ resolution: {integrity: sha512-gB0gkNafnonOw0obSTEGZTT86IuhILt2Wfx0mWH/1Au83kybTayroZ/V6nS25mN7u8ASy+5fMhgB3XPNrOZdmA==}
+ engines: {node: '>= 20'}
+ hasBin: true
+
math-intrinsics@1.1.0:
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
engines: {node: '>= 0.4'}
@@ -3005,8 +3041,8 @@ packages:
mdast-util-find-and-replace@3.0.2:
resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==}
- mdast-util-from-markdown@2.0.2:
- resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==}
+ mdast-util-from-markdown@2.0.3:
+ resolution: {integrity: sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==}
mdast-util-gfm-autolink-literal@2.0.1:
resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==}
@@ -3064,8 +3100,8 @@ packages:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
engines: {node: '>= 8'}
- mermaid@11.12.2:
- resolution: {integrity: sha512-n34QPDPEKmaeCG4WDMGy0OT6PSyxKCfy2pJgShP+Qow2KLrvWjclwbc3yXfSIf4BanqWEhQEpngWwNp/XhZt6w==}
+ mermaid@11.14.0:
+ resolution: {integrity: sha512-GSGloRsBs+JINmmhl0JDwjpuezCsHB4WGI4NASHxL3fHo3o/BRXTxhDLKnln8/Q0lRFRyDdEjmk1/d5Sn1Xz8g==}
micromark-core-commonmark@2.0.3:
resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==}
@@ -3225,14 +3261,14 @@ packages:
resolution: {integrity: sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==}
engines: {node: '>=0.10.0'}
- mlly@1.8.0:
- resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==}
+ mlly@1.8.2:
+ resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==}
- motion-dom@12.23.23:
- resolution: {integrity: sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA==}
+ motion-dom@12.38.0:
+ resolution: {integrity: sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA==}
- motion-utils@12.23.6:
- resolution: {integrity: sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==}
+ motion-utils@12.36.0:
+ resolution: {integrity: sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg==}
motion@12.23.26:
resolution: {integrity: sha512-Ll8XhVxY8LXMVYTCfme27WH2GjBrCIzY4+ndr5QKxsK+YwCtOi2B/oBi5jcIbik5doXuWT/4KKDOVAZJkeY5VQ==}
@@ -3308,8 +3344,8 @@ packages:
oniguruma-parser@0.12.1:
resolution: {integrity: sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==}
- oniguruma-to-es@4.3.4:
- resolution: {integrity: sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA==}
+ oniguruma-to-es@4.3.5:
+ resolution: {integrity: sha512-Zjygswjpsewa0NLTsiizVuMQZbp0MDyM6lIt66OxsF21npUDlzpHi1Mgb/qhQdkb+dWFTzJmFbEWdvZgRho8eQ==}
optionator@0.9.4:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
@@ -3503,8 +3539,9 @@ packages:
proto-list@1.2.4:
resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==}
- proxy-from-env@1.1.0:
- resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
+ proxy-from-env@2.1.0:
+ resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==}
+ engines: {node: '>=10'}
psl@1.15.0:
resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==}
@@ -3617,8 +3654,8 @@ packages:
peerDependencies:
react: ^19.2.3
- react-draggable@4.4.6:
- resolution: {integrity: sha512-LtY5Xw1zTPqHkVmtM3X8MUOxNDOUhv/khTgBgrUvwaS064bwVvxT+q5El0uUFNx5IEPKXuRejr7UqLwBIg5pdw==}
+ react-draggable@4.5.0:
+ resolution: {integrity: sha512-VC+HBLEZ0XJxnOxVAZsdRi8rD04Iz3SiiKOoYzamjylUcju/hP9np/aZdLHf/7WOD268WMoNJMvYfB5yAK45cw==}
peerDependencies:
react: '>= 16.3.0'
react-dom: '>= 16.3.0'
@@ -3629,17 +3666,16 @@ packages:
peerDependencies:
react: '>= 16.8'
- react-error-boundary@6.0.1:
- resolution: {integrity: sha512-zArgQpjJUN1ZLMEKWtifxQweW3yfvwL5j2nh3Pesze1qG6r5oCDMy/TA97bUF01wy4xCeeL4/pd8GHmvEsP3Bg==}
+ react-error-boundary@6.1.1:
+ resolution: {integrity: sha512-BrYwPOdXi5mqkk5lw+Uvt0ThHx32rCt3BkukS4X23A2AIWDPSGX6iaWTc0y9TU/mHDA/6qOSGel+B2ERkOvD1w==}
peerDependencies:
react: ^18.0.0 || ^19.0.0
- react-dom: ^18.0.0 || ^19.0.0
react-fast-compare@3.2.2:
resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==}
- react-hotkeys-hook@5.2.1:
- resolution: {integrity: sha512-xbKh6zJxd/vJHT4Bw4+0pBD662Fk20V+VFhLqciCg+manTVO4qlqRqiwFOYelfHN9dBvWj9vxaPkSS26ZSIJGg==}
+ react-hotkeys-hook@5.2.4:
+ resolution: {integrity: sha512-BgKg+A1+TawkYluh5Bo4cTmcgMN5L29uhJbDUQdHwPX+qgXRjIPYU5kIDHyxnAwCkCBiu9V5OpB2mpyeluVF2A==}
peerDependencies:
react: '>=16.8.0'
react-dom: '>=16.8.0'
@@ -3664,8 +3700,8 @@ packages:
react:
optional: true
- react-rnd@10.5.2:
- resolution: {integrity: sha512-0Tm4x7k7pfHf2snewJA8x7Nwgt3LV+58MVEWOVsFjk51eYruFEa6Wy7BNdxt4/lH0wIRsu7Gm3KjSXY2w7YaNw==}
+ react-rnd@10.5.3:
+ resolution: {integrity: sha512-s/sIT3pGZnQ+57egijkTp9mizjIWrJz68Pq6yd+F/wniFY3IriML18dUXnQe/HP9uMiJ+9MAp44hljG99fZu6Q==}
peerDependencies:
react: '>=16.3.0'
react-dom: '>=16.3.0'
@@ -3795,8 +3831,8 @@ packages:
deprecated: Rimraf versions prior to v4 are no longer supported
hasBin: true
- robust-predicates@3.0.2:
- resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==}
+ robust-predicates@3.0.3:
+ resolution: {integrity: sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==}
rollup@4.54.0:
resolution: {integrity: sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==}
@@ -3858,19 +3894,22 @@ packages:
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
engines: {node: '>=8'}
- shiki-stream@0.1.3:
- resolution: {integrity: sha512-pDIqmaP/zJWHNV8bJKp0tD0CZ6OkF+lWTIvmNRLktlTjBjN3+durr19JarS657U1oSEf/WrSYmdzwr9CeD6m2Q==}
+ shiki-stream@0.1.4:
+ resolution: {integrity: sha512-4pz6JGSDmVTTkPJ/ueixHkFAXY4ySCc+unvCaDZV7hqq/sdJZirRxgIXSuNSKgiFlGTgRR97sdu2R8K55sPsrw==}
peerDependencies:
react: ^19.0.0
+ solid-js: ^1.9.0
vue: ^3.2.0
peerDependenciesMeta:
react:
optional: true
+ solid-js:
+ optional: true
vue:
optional: true
- shiki@3.20.0:
- resolution: {integrity: sha512-kgCOlsnyWb+p0WU+01RjkCH+eBVsjL1jOwUYWv0YDWkM2/A46+LDKVs5yZCUXjJG6bj4ndFoAg5iLIIue6dulg==}
+ shiki@3.23.0:
+ resolution: {integrity: sha512-55Dj73uq9ZXL5zyeRPzHQsK7Nbyt6Y10k5s7OjuFZGMhpp4r/rsLBH0o/0fstIzX1Lep9VxefWljK/SKCzygIA==}
siginfo@2.0.0:
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
@@ -3967,8 +4006,8 @@ packages:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
- swr@2.3.8:
- resolution: {integrity: sha512-gaCPRVoMq8WGDcWj9p4YWzCMPHzE0WNl6W8ADIx9c3JBEIdMkJGMzW+uzXvxHMltwcYACr9jP+32H8/hgwMR7w==}
+ swr@2.4.1:
+ resolution: {integrity: sha512-2CC6CiKQtEwaEeNiqWTAw9PGykW8SR5zZX8MZk6TeAvEAnVS7Visz8WzphqgtQ8v2xz/4Q5K+j+SeMaKXeeQIA==}
peerDependencies:
react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
@@ -4010,8 +4049,8 @@ packages:
tinyexec@0.3.2:
resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
- tinyexec@1.0.2:
- resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==}
+ tinyexec@1.1.1:
+ resolution: {integrity: sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==}
engines: {node: '>=18'}
tinyglobby@0.2.15:
@@ -4087,8 +4126,8 @@ packages:
engines: {node: '>=14.17'}
hasBin: true
- ufo@1.6.1:
- resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==}
+ ufo@1.6.3:
+ resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==}
undici-types@6.21.0:
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
@@ -4121,8 +4160,8 @@ packages:
unist-util-visit-parents@6.0.2:
resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==}
- unist-util-visit@5.0.0:
- resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==}
+ unist-util-visit@5.1.0:
+ resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==}
universalify@0.2.0:
resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==}
@@ -4289,9 +4328,6 @@ packages:
resolution: {integrity: sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==}
hasBin: true
- vscode-uri@3.0.8:
- resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==}
-
vscode-uri@3.1.0:
resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==}
@@ -4487,15 +4523,15 @@ snapshots:
'@jridgewell/gen-mapping': 0.3.13
'@jridgewell/trace-mapping': 0.3.31
- '@ant-design/colors@8.0.0':
+ '@ant-design/colors@8.0.1':
dependencies:
- '@ant-design/fast-color': 3.0.0
+ '@ant-design/fast-color': 3.0.1
- '@ant-design/cssinjs-utils@2.0.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
+ '@ant-design/cssinjs-utils@2.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies:
- '@ant-design/cssinjs': 2.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
- '@babel/runtime': 7.28.4
- '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@ant-design/cssinjs': 2.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@babel/runtime': 7.29.2
+ '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
@@ -4511,22 +4547,34 @@ snapshots:
react-dom: 19.2.3(react@19.2.3)
stylis: 4.3.6
- '@ant-design/fast-color@3.0.0': {}
+ '@ant-design/cssinjs@2.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
+ dependencies:
+ '@babel/runtime': 7.29.2
+ '@emotion/hash': 0.8.0
+ '@emotion/unitless': 0.7.5
+ '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ clsx: 2.1.1
+ csstype: 3.2.3
+ react: 19.2.3
+ react-dom: 19.2.3(react@19.2.3)
+ stylis: 4.3.6
+
+ '@ant-design/fast-color@3.0.1': {}
'@ant-design/icons-svg@4.4.2': {}
- '@ant-design/icons@6.1.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
+ '@ant-design/icons@6.1.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies:
- '@ant-design/colors': 8.0.0
+ '@ant-design/colors': 8.0.1
'@ant-design/icons-svg': 4.4.2
- '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
clsx: 2.1.1
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
'@ant-design/react-slick@2.0.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies:
- '@babel/runtime': 7.28.4
+ '@babel/runtime': 7.29.2
clsx: 2.1.1
json2mq: 0.2.0
react: 19.2.3
@@ -4536,7 +4584,7 @@ snapshots:
'@antfu/install-pkg@1.1.0':
dependencies:
package-manager-detector: 1.6.0
- tinyexec: 1.0.2
+ tinyexec: 1.1.1
'@asamuzakjp/css-color@3.2.0':
dependencies:
@@ -4579,6 +4627,8 @@ snapshots:
'@babel/runtime@7.28.4': {}
+ '@babel/runtime@7.29.2': {}
+
'@babel/template@7.27.2':
dependencies:
'@babel/code-frame': 7.27.1
@@ -4602,24 +4652,23 @@ snapshots:
'@babel/helper-string-parser': 7.27.1
'@babel/helper-validator-identifier': 7.28.5
- '@base-ui/react@1.0.0(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
+ '@base-ui/react@1.3.0(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies:
- '@babel/runtime': 7.28.4
- '@base-ui/utils': 0.2.3(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
- '@floating-ui/react-dom': 2.1.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
- '@floating-ui/utils': 0.2.10
+ '@babel/runtime': 7.29.2
+ '@base-ui/utils': 0.2.6(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@floating-ui/react-dom': 2.1.8(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@floating-ui/utils': 0.2.11
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
- reselect: 5.1.1
tabbable: 6.4.0
use-sync-external-store: 1.6.0(react@19.2.3)
optionalDependencies:
'@types/react': 19.2.7
- '@base-ui/utils@0.2.3(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
+ '@base-ui/utils@0.2.6(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies:
- '@babel/runtime': 7.28.4
- '@floating-ui/utils': 0.2.10
+ '@babel/runtime': 7.29.2
+ '@floating-ui/utils': 0.2.11
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
reselect: 5.1.1
@@ -4629,24 +4678,22 @@ snapshots:
'@bcoe/v8-coverage@0.2.3': {}
- '@braintree/sanitize-url@7.1.1': {}
+ '@braintree/sanitize-url@7.1.2': {}
- '@chevrotain/cst-dts-gen@11.0.3':
+ '@chevrotain/cst-dts-gen@12.0.0':
dependencies:
- '@chevrotain/gast': 11.0.3
- '@chevrotain/types': 11.0.3
- lodash-es: 4.17.21
+ '@chevrotain/gast': 12.0.0
+ '@chevrotain/types': 12.0.0
- '@chevrotain/gast@11.0.3':
+ '@chevrotain/gast@12.0.0':
dependencies:
- '@chevrotain/types': 11.0.3
- lodash-es: 4.17.21
+ '@chevrotain/types': 12.0.0
- '@chevrotain/regexp-to-ast@11.0.3': {}
+ '@chevrotain/regexp-to-ast@12.0.0': {}
- '@chevrotain/types@11.0.3': {}
+ '@chevrotain/types@12.0.0': {}
- '@chevrotain/utils@11.0.3': {}
+ '@chevrotain/utils@12.0.0': {}
'@csstools/color-helpers@5.1.0': {}
@@ -4881,30 +4928,30 @@ snapshots:
'@eslint/js@8.57.1': {}
- '@floating-ui/core@1.7.3':
+ '@floating-ui/core@1.7.5':
dependencies:
- '@floating-ui/utils': 0.2.10
+ '@floating-ui/utils': 0.2.11
- '@floating-ui/dom@1.7.4':
+ '@floating-ui/dom@1.7.6':
dependencies:
- '@floating-ui/core': 1.7.3
- '@floating-ui/utils': 0.2.10
+ '@floating-ui/core': 1.7.5
+ '@floating-ui/utils': 0.2.11
- '@floating-ui/react-dom@2.1.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
+ '@floating-ui/react-dom@2.1.8(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies:
- '@floating-ui/dom': 1.7.4
+ '@floating-ui/dom': 1.7.6
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
- '@floating-ui/react@0.27.16(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
+ '@floating-ui/react@0.27.19(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies:
- '@floating-ui/react-dom': 2.1.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
- '@floating-ui/utils': 0.2.10
+ '@floating-ui/react-dom': 2.1.8(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@floating-ui/utils': 0.2.11
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
tabbable: 6.4.0
- '@floating-ui/utils@0.2.10': {}
+ '@floating-ui/utils@0.2.11': {}
'@giscus/react@3.1.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies:
@@ -4930,7 +4977,7 @@ snapshots:
dependencies:
'@antfu/install-pkg': 1.1.0
'@iconify/types': 2.0.0
- mlly: 1.8.0
+ mlly: 1.8.2
'@intlify/core-base@9.14.5':
dependencies:
@@ -4971,11 +5018,11 @@ snapshots:
'@kurkle/color@0.3.4': {}
- '@lit-labs/ssr-dom-shim@1.5.0': {}
+ '@lit-labs/ssr-dom-shim@1.5.1': {}
'@lit/reactive-element@2.1.2':
dependencies:
- '@lit-labs/ssr-dom-shim': 1.5.0
+ '@lit-labs/ssr-dom-shim': 1.5.1
'@lobehub/emojilib@1.0.0': {}
@@ -4984,7 +5031,7 @@ snapshots:
'@lobehub/emojilib': 1.0.0
antd-style: 4.1.0(@types/react@19.2.7)(antd@6.1.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
emoji-regex: 10.6.0
- es-toolkit: 1.43.0
+ es-toolkit: 1.45.1
lucide-react: 0.562.0(react@19.2.3)
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
@@ -5009,8 +5056,8 @@ snapshots:
'@lobehub/ui@4.9.2(@lobehub/fluent-emoji@4.1.0(@types/react@19.2.7)(antd@6.1.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@lobehub/icons@4.0.2)(@types/mdast@4.0.4)(@types/react@19.2.7)(antd@6.1.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(micromark-util-types@2.0.2)(micromark@4.0.2)(motion@12.23.26(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vue@3.5.26(typescript@5.6.3))':
dependencies:
- '@ant-design/cssinjs': 2.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
- '@base-ui/react': 1.0.0(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@ant-design/cssinjs': 2.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@base-ui/react': 1.3.0(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@dnd-kit/core': 6.3.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@dnd-kit/modifiers': 9.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)
'@dnd-kit/sortable': 10.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)
@@ -5018,32 +5065,32 @@ snapshots:
'@emoji-mart/data': 1.2.1
'@emoji-mart/react': 1.1.1(emoji-mart@5.6.0)(react@19.2.3)
'@emotion/is-prop-valid': 1.4.0
- '@floating-ui/react': 0.27.16(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@floating-ui/react': 0.27.19(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@giscus/react': 3.1.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@lobehub/fluent-emoji': 4.1.0(@types/react@19.2.7)(antd@6.1.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@lobehub/icons': 4.0.2(@lobehub/ui@4.9.2)(@types/react@19.2.7)(antd@6.1.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@mdx-js/mdx': 3.1.1
'@mdx-js/react': 3.1.1(@types/react@19.2.7)(react@19.2.3)
'@radix-ui/react-slot': 1.2.4(@types/react@19.2.7)(react@19.2.3)
- '@shikijs/core': 3.20.0
- '@shikijs/transformers': 3.20.0
+ '@shikijs/core': 3.23.0
+ '@shikijs/transformers': 3.23.0
'@splinetool/runtime': 0.9.526
- ahooks: 3.9.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ ahooks: 3.9.7(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
antd: 6.1.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
antd-style: 4.1.0(@types/react@19.2.7)(antd@6.1.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
chroma-js: 3.2.0
class-variance-authority: 0.7.1
clsx: 2.1.1
- dayjs: 1.11.19
+ dayjs: 1.11.20
emoji-mart: 5.6.0
- es-toolkit: 1.43.0
+ es-toolkit: 1.45.1
fast-deep-equal: 3.1.3
- immer: 11.1.3
- katex: 0.16.27
+ immer: 11.1.4
+ katex: 0.16.45
leva: 0.10.1(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
lucide-react: 0.562.0(react@19.2.3)
- marked: 17.0.1
- mermaid: 11.12.2
+ marked: 17.0.6
+ mermaid: 11.14.0
motion: 12.23.26(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
numeral: 2.0.6
polished: 4.3.1
@@ -5057,11 +5104,11 @@ snapshots:
react: 19.2.3
react-avatar-editor: 14.0.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
react-dom: 19.2.3(react@19.2.3)
- react-error-boundary: 6.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
- react-hotkeys-hook: 5.2.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ react-error-boundary: 6.1.1(react@19.2.3)
+ react-hotkeys-hook: 5.2.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
react-markdown: 10.1.0(@types/react@19.2.7)(react@19.2.3)
react-merge-refs: 3.0.2(react@19.2.3)
- react-rnd: 10.5.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ react-rnd: 10.5.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
react-zoom-pan-pinch: 3.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
rehype-github-alerts: 4.2.0
rehype-katex: 7.0.1
@@ -5071,9 +5118,9 @@ snapshots:
remark-gfm: 4.0.1
remark-github: 12.0.0
remark-math: 6.0.0
- shiki: 3.20.0
- shiki-stream: 0.1.3(react@19.2.3)(vue@3.5.26(typescript@5.6.3))
- swr: 2.3.8(react@19.2.3)
+ shiki: 3.23.0
+ shiki-stream: 0.1.4(react@19.2.3)(vue@3.5.26(typescript@5.6.3))
+ swr: 2.4.1(react@19.2.3)
ts-md5: 2.0.1
unified: 11.0.5
url-join: 5.0.0
@@ -5085,6 +5132,7 @@ snapshots:
- '@types/react-dom'
- micromark
- micromark-util-types
+ - solid-js
- supports-color
- vue
@@ -5094,7 +5142,7 @@ snapshots:
'@types/estree-jsx': 1.0.5
'@types/hast': 3.0.4
'@types/mdx': 2.0.13
- acorn: 8.15.0
+ acorn: 8.16.0
collapse-white-space: 2.1.0
devlop: 1.1.0
estree-util-is-identifier-name: 3.0.0
@@ -5103,7 +5151,7 @@ snapshots:
hast-util-to-jsx-runtime: 2.3.6
markdown-extensions: 2.0.0
recma-build-jsx: 1.0.0
- recma-jsx: 1.0.1(acorn@8.15.0)
+ recma-jsx: 1.0.1(acorn@8.16.0)
recma-stringify: 1.0.0
rehype-recma: 1.0.0
remark-mdx: 3.1.1
@@ -5113,7 +5161,7 @@ snapshots:
unified: 11.0.5
unist-util-position-from-estree: 2.0.0
unist-util-stringify-position: 4.0.0
- unist-util-visit: 5.0.0
+ unist-util-visit: 5.1.0
vfile: 6.0.3
transitivePeerDependencies:
- supports-color
@@ -5124,9 +5172,9 @@ snapshots:
'@types/react': 19.2.7
react: 19.2.3
- '@mermaid-js/parser@0.6.3':
+ '@mermaid-js/parser@1.1.0':
dependencies:
- langium: 3.3.1
+ langium: 4.2.2
'@nodelib/fs.scandir@2.1.5':
dependencies:
@@ -5145,7 +5193,7 @@ snapshots:
'@pkgjs/parseargs@0.11.0':
optional: true
- '@primer/octicons@19.21.1':
+ '@primer/octicons@19.23.1':
dependencies:
object-assign: 4.1.1
@@ -5192,7 +5240,7 @@ snapshots:
'@radix-ui/react-popper@1.2.8(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies:
- '@floating-ui/react-dom': 2.1.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@floating-ui/react-dom': 2.1.8(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@radix-ui/react-arrow': 1.1.7(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.3)
'@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.3)
@@ -5341,46 +5389,46 @@ snapshots:
'@radix-ui/rect@1.1.1': {}
- '@rc-component/async-validator@5.0.4':
+ '@rc-component/async-validator@5.1.0':
dependencies:
- '@babel/runtime': 7.28.4
+ '@babel/runtime': 7.29.2
'@rc-component/cascader@1.10.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies:
'@rc-component/select': 1.4.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@rc-component/tree': 1.1.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
- '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
clsx: 2.1.1
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
'@rc-component/checkbox@1.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies:
- '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
clsx: 2.1.1
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
'@rc-component/collapse@1.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies:
- '@babel/runtime': 7.28.4
+ '@babel/runtime': 7.29.2
'@rc-component/motion': 1.1.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
- '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
clsx: 2.1.1
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
'@rc-component/color-picker@3.0.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies:
- '@ant-design/fast-color': 3.0.0
- '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@ant-design/fast-color': 3.0.1
+ '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
clsx: 2.1.1
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
'@rc-component/context@2.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies:
- '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
@@ -5388,7 +5436,7 @@ snapshots:
dependencies:
'@rc-component/motion': 1.1.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@rc-component/portal': 2.2.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
- '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
clsx: 2.1.1
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
@@ -5397,23 +5445,23 @@ snapshots:
dependencies:
'@rc-component/motion': 1.1.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@rc-component/portal': 2.2.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
- '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
clsx: 2.1.1
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
'@rc-component/dropdown@1.0.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies:
- '@rc-component/trigger': 3.8.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
- '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/trigger': 3.9.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
clsx: 2.1.1
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
- '@rc-component/form@1.6.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
+ '@rc-component/form@1.6.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies:
- '@rc-component/async-validator': 5.0.4
- '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/async-validator': 5.1.0
+ '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
clsx: 2.1.1
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
@@ -5422,22 +5470,22 @@ snapshots:
dependencies:
'@rc-component/motion': 1.1.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@rc-component/portal': 2.2.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
- '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
clsx: 2.1.1
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
'@rc-component/input-number@1.6.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies:
- '@rc-component/mini-decimal': 1.1.0
- '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/mini-decimal': 1.1.3
+ '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
clsx: 2.1.1
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
'@rc-component/input@1.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies:
- '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
clsx: 2.1.1
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
@@ -5447,8 +5495,8 @@ snapshots:
'@rc-component/input': 1.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@rc-component/menu': 1.2.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@rc-component/textarea': 1.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
- '@rc-component/trigger': 3.8.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
- '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/trigger': 3.9.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
clsx: 2.1.1
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
@@ -5457,68 +5505,68 @@ snapshots:
dependencies:
'@rc-component/motion': 1.1.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@rc-component/overflow': 1.0.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
- '@rc-component/trigger': 3.8.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
- '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/trigger': 3.9.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
clsx: 2.1.1
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
- '@rc-component/mini-decimal@1.1.0':
+ '@rc-component/mini-decimal@1.1.3':
dependencies:
- '@babel/runtime': 7.28.4
+ '@babel/runtime': 7.29.2
'@rc-component/motion@1.1.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies:
- '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
clsx: 2.1.1
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
'@rc-component/mutate-observer@2.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies:
- '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
'@rc-component/notification@1.2.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies:
'@rc-component/motion': 1.1.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
- '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
clsx: 2.1.1
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
'@rc-component/overflow@1.0.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies:
- '@babel/runtime': 7.28.4
- '@rc-component/resize-observer': 1.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
- '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@babel/runtime': 7.29.2
+ '@rc-component/resize-observer': 1.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
clsx: 2.1.1
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
'@rc-component/pagination@1.2.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies:
- '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
clsx: 2.1.1
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
- '@rc-component/picker@1.9.0(dayjs@1.11.19)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
+ '@rc-component/picker@1.9.1(dayjs@1.11.20)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies:
'@rc-component/overflow': 1.0.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
- '@rc-component/resize-observer': 1.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
- '@rc-component/trigger': 3.8.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
- '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/resize-observer': 1.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/trigger': 3.9.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
clsx: 2.1.1
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
optionalDependencies:
- dayjs: 1.11.19
+ dayjs: 1.11.20
'@rc-component/portal@1.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies:
- '@babel/runtime': 7.28.4
+ '@babel/runtime': 7.29.2
classnames: 2.5.1
rc-util: 5.44.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
react: 19.2.3
@@ -5526,42 +5574,42 @@ snapshots:
'@rc-component/portal@2.2.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies:
- '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
clsx: 2.1.1
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
'@rc-component/progress@1.0.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies:
- '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
clsx: 2.1.1
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
'@rc-component/qrcode@1.1.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies:
- '@babel/runtime': 7.28.4
+ '@babel/runtime': 7.29.2
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
'@rc-component/rate@1.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies:
- '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
clsx: 2.1.1
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
- '@rc-component/resize-observer@1.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
+ '@rc-component/resize-observer@1.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies:
- '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
'@rc-component/segmented@1.3.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies:
- '@babel/runtime': 7.28.4
+ '@babel/runtime': 7.29.2
'@rc-component/motion': 1.1.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
- '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
clsx: 2.1.1
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
@@ -5569,8 +5617,8 @@ snapshots:
'@rc-component/select@1.4.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies:
'@rc-component/overflow': 1.0.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
- '@rc-component/trigger': 3.8.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
- '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/trigger': 3.9.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@rc-component/virtual-list': 1.0.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
clsx: 2.1.1
react: 19.2.3
@@ -5578,21 +5626,21 @@ snapshots:
'@rc-component/slider@1.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies:
- '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
clsx: 2.1.1
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
'@rc-component/steps@1.2.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies:
- '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
clsx: 2.1.1
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
'@rc-component/switch@1.0.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies:
- '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
clsx: 2.1.1
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
@@ -5600,8 +5648,8 @@ snapshots:
'@rc-component/table@1.9.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies:
'@rc-component/context': 2.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
- '@rc-component/resize-observer': 1.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
- '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/resize-observer': 1.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@rc-component/virtual-list': 1.0.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
clsx: 2.1.1
react: 19.2.3
@@ -5612,8 +5660,8 @@ snapshots:
'@rc-component/dropdown': 1.0.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@rc-component/menu': 1.2.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@rc-component/motion': 1.1.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
- '@rc-component/resize-observer': 1.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
- '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/resize-observer': 1.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
clsx: 2.1.1
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
@@ -5621,16 +5669,16 @@ snapshots:
'@rc-component/textarea@1.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies:
'@rc-component/input': 1.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
- '@rc-component/resize-observer': 1.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
- '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/resize-observer': 1.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
clsx: 2.1.1
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
'@rc-component/tooltip@1.4.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies:
- '@rc-component/trigger': 3.8.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
- '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/trigger': 3.9.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
clsx: 2.1.1
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
@@ -5638,8 +5686,8 @@ snapshots:
'@rc-component/tour@2.2.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies:
'@rc-component/portal': 2.2.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
- '@rc-component/trigger': 3.8.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
- '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/trigger': 3.9.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
clsx: 2.1.1
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
@@ -5648,7 +5696,7 @@ snapshots:
dependencies:
'@rc-component/select': 1.4.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@rc-component/tree': 1.1.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
- '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
clsx: 2.1.1
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
@@ -5656,15 +5704,15 @@ snapshots:
'@rc-component/tree@1.1.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies:
'@rc-component/motion': 1.1.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
- '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@rc-component/virtual-list': 1.0.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
clsx: 2.1.1
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
- '@rc-component/trigger@2.3.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
+ '@rc-component/trigger@2.3.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies:
- '@babel/runtime': 7.28.4
+ '@babel/runtime': 7.29.2
'@rc-component/portal': 1.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
classnames: 2.5.1
rc-motion: 2.9.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
@@ -5673,23 +5721,30 @@ snapshots:
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
- '@rc-component/trigger@3.8.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
+ '@rc-component/trigger@3.9.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies:
'@rc-component/motion': 1.1.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@rc-component/portal': 2.2.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
- '@rc-component/resize-observer': 1.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
- '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/resize-observer': 1.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
clsx: 2.1.1
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
'@rc-component/upload@1.1.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies:
- '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
clsx: 2.1.1
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
+ '@rc-component/util@1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
+ dependencies:
+ is-mobile: 5.0.0
+ react: 19.2.3
+ react-dom: 19.2.3(react@19.2.3)
+ react-is: 18.3.1
+
'@rc-component/util@1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies:
is-mobile: 5.0.0
@@ -5699,9 +5754,9 @@ snapshots:
'@rc-component/virtual-list@1.0.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies:
- '@babel/runtime': 7.28.4
- '@rc-component/resize-observer': 1.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
- '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@babel/runtime': 7.29.2
+ '@rc-component/resize-observer': 1.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
clsx: 2.1.1
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
@@ -5772,38 +5827,38 @@ snapshots:
'@rollup/rollup-win32-x64-msvc@4.54.0':
optional: true
- '@shikijs/core@3.20.0':
+ '@shikijs/core@3.23.0':
dependencies:
- '@shikijs/types': 3.20.0
+ '@shikijs/types': 3.23.0
'@shikijs/vscode-textmate': 10.0.2
'@types/hast': 3.0.4
hast-util-to-html: 9.0.5
- '@shikijs/engine-javascript@3.20.0':
+ '@shikijs/engine-javascript@3.23.0':
dependencies:
- '@shikijs/types': 3.20.0
+ '@shikijs/types': 3.23.0
'@shikijs/vscode-textmate': 10.0.2
- oniguruma-to-es: 4.3.4
+ oniguruma-to-es: 4.3.5
- '@shikijs/engine-oniguruma@3.20.0':
+ '@shikijs/engine-oniguruma@3.23.0':
dependencies:
- '@shikijs/types': 3.20.0
+ '@shikijs/types': 3.23.0
'@shikijs/vscode-textmate': 10.0.2
- '@shikijs/langs@3.20.0':
+ '@shikijs/langs@3.23.0':
dependencies:
- '@shikijs/types': 3.20.0
+ '@shikijs/types': 3.23.0
- '@shikijs/themes@3.20.0':
+ '@shikijs/themes@3.23.0':
dependencies:
- '@shikijs/types': 3.20.0
+ '@shikijs/types': 3.23.0
- '@shikijs/transformers@3.20.0':
+ '@shikijs/transformers@3.23.0':
dependencies:
- '@shikijs/core': 3.20.0
- '@shikijs/types': 3.20.0
+ '@shikijs/core': 3.23.0
+ '@shikijs/types': 3.23.0
- '@shikijs/types@3.20.0':
+ '@shikijs/types@3.23.0':
dependencies:
'@shikijs/vscode-textmate': 10.0.2
'@types/hast': 3.0.4
@@ -5819,6 +5874,8 @@ snapshots:
dependencies:
react: 19.2.3
+ '@stripe/stripe-js@9.0.1': {}
+
'@tanstack/virtual-core@3.13.23': {}
'@tanstack/vue-virtual@3.13.23(vue@3.5.26(typescript@5.6.3))':
@@ -5891,7 +5948,7 @@ snapshots:
'@types/d3-selection@3.0.11': {}
- '@types/d3-shape@3.1.7':
+ '@types/d3-shape@3.1.8':
dependencies:
'@types/d3-path': 3.1.1
@@ -5936,14 +5993,14 @@ snapshots:
'@types/d3-scale': 4.0.9
'@types/d3-scale-chromatic': 3.1.0
'@types/d3-selection': 3.0.11
- '@types/d3-shape': 3.1.7
+ '@types/d3-shape': 3.1.8
'@types/d3-time': 3.0.4
'@types/d3-time-format': 4.0.3
'@types/d3-timer': 3.0.2
'@types/d3-transition': 3.0.9
'@types/d3-zoom': 3.0.8
- '@types/debug@4.1.12':
+ '@types/debug@4.1.13':
dependencies:
'@types/ms': 2.1.0
@@ -5967,7 +6024,7 @@ snapshots:
'@types/js-cookie@3.0.6': {}
- '@types/katex@0.16.7': {}
+ '@types/katex@0.16.8': {}
'@types/mdast@4.0.4':
dependencies:
@@ -6084,6 +6141,11 @@ snapshots:
'@ungap/structured-clone@1.3.0': {}
+ '@upsetjs/venn.js@2.0.0':
+ optionalDependencies:
+ d3-selection: 3.0.0
+ d3-transition: 3.0.1(d3-selection@3.0.0)
+
'@use-gesture/core@10.3.1': {}
'@use-gesture/react@10.3.1(react@19.2.3)':
@@ -6270,20 +6332,26 @@ snapshots:
dependencies:
acorn: 8.15.0
+ acorn-jsx@5.3.2(acorn@8.16.0):
+ dependencies:
+ acorn: 8.16.0
+
acorn@8.15.0: {}
+ acorn@8.16.0: {}
+
adler-32@1.3.1: {}
agent-base@7.1.4: {}
- ahooks@3.9.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
+ ahooks@3.9.7(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
dependencies:
- '@babel/runtime': 7.28.4
+ '@babel/runtime': 7.29.2
'@types/js-cookie': 3.0.6
- dayjs: 1.11.19
+ dayjs: 1.11.20
intersection-observer: 0.12.2
js-cookie: 3.0.5
- lodash: 4.17.21
+ lodash: 4.18.1
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
react-fast-compare: 3.2.2
@@ -6329,13 +6397,13 @@ snapshots:
antd@6.1.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
dependencies:
- '@ant-design/colors': 8.0.0
- '@ant-design/cssinjs': 2.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
- '@ant-design/cssinjs-utils': 2.0.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
- '@ant-design/fast-color': 3.0.0
- '@ant-design/icons': 6.1.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@ant-design/colors': 8.0.1
+ '@ant-design/cssinjs': 2.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@ant-design/cssinjs-utils': 2.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@ant-design/fast-color': 3.0.1
+ '@ant-design/icons': 6.1.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@ant-design/react-slick': 2.0.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
- '@babel/runtime': 7.28.4
+ '@babel/runtime': 7.29.2
'@rc-component/cascader': 1.10.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@rc-component/checkbox': 1.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@rc-component/collapse': 1.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
@@ -6343,7 +6411,7 @@ snapshots:
'@rc-component/dialog': 1.5.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@rc-component/drawer': 1.3.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@rc-component/dropdown': 1.0.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
- '@rc-component/form': 1.6.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/form': 1.6.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@rc-component/image': 1.5.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@rc-component/input': 1.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@rc-component/input-number': 1.6.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
@@ -6353,11 +6421,11 @@ snapshots:
'@rc-component/mutate-observer': 2.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@rc-component/notification': 1.2.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@rc-component/pagination': 1.2.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
- '@rc-component/picker': 1.9.0(dayjs@1.11.19)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/picker': 1.9.1(dayjs@1.11.20)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@rc-component/progress': 1.0.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@rc-component/qrcode': 1.1.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@rc-component/rate': 1.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
- '@rc-component/resize-observer': 1.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/resize-observer': 1.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@rc-component/segmented': 1.3.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@rc-component/select': 1.4.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@rc-component/slider': 1.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
@@ -6370,11 +6438,11 @@ snapshots:
'@rc-component/tour': 2.2.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@rc-component/tree': 1.1.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@rc-component/tree-select': 1.5.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
- '@rc-component/trigger': 3.8.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/trigger': 3.9.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@rc-component/upload': 1.1.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
- '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
clsx: 2.1.1
- dayjs: 1.11.19
+ dayjs: 1.11.20
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
scroll-into-view-if-needed: 3.1.0
@@ -6416,11 +6484,11 @@ snapshots:
postcss: 8.5.6
postcss-value-parser: 4.2.0
- axios@1.13.5:
+ axios@1.15.0:
dependencies:
follow-redirects: 1.15.11
form-data: 4.0.5
- proxy-from-env: 1.1.0
+ proxy-from-env: 2.1.0
transitivePeerDependencies:
- debug
@@ -6510,19 +6578,18 @@ snapshots:
check-error@2.1.3: {}
- chevrotain-allstar@0.3.1(chevrotain@11.0.3):
+ chevrotain-allstar@0.4.1(chevrotain@12.0.0):
dependencies:
- chevrotain: 11.0.3
- lodash-es: 4.17.22
+ chevrotain: 12.0.0
+ lodash-es: 4.18.1
- chevrotain@11.0.3:
+ chevrotain@12.0.0:
dependencies:
- '@chevrotain/cst-dts-gen': 11.0.3
- '@chevrotain/gast': 11.0.3
- '@chevrotain/regexp-to-ast': 11.0.3
- '@chevrotain/types': 11.0.3
- '@chevrotain/utils': 11.0.3
- lodash-es: 4.17.21
+ '@chevrotain/cst-dts-gen': 12.0.0
+ '@chevrotain/gast': 12.0.0
+ '@chevrotain/regexp-to-ast': 12.0.0
+ '@chevrotain/types': 12.0.0
+ '@chevrotain/utils': 12.0.0
chokidar@3.6.0:
dependencies:
@@ -6554,8 +6621,6 @@ snapshots:
strip-ansi: 6.0.1
wrap-ansi: 6.2.0
- clsx@1.2.1: {}
-
clsx@2.1.1: {}
codepage@1.15.0: {}
@@ -6630,17 +6695,17 @@ snapshots:
csstype@3.2.3: {}
- cytoscape-cose-bilkent@4.1.0(cytoscape@3.33.1):
+ cytoscape-cose-bilkent@4.1.0(cytoscape@3.33.2):
dependencies:
cose-base: 1.0.3
- cytoscape: 3.33.1
+ cytoscape: 3.33.2
- cytoscape-fcose@2.2.0(cytoscape@3.33.1):
+ cytoscape-fcose@2.2.0(cytoscape@3.33.2):
dependencies:
cose-base: 2.2.0
- cytoscape: 3.33.1
+ cytoscape: 3.33.2
- cytoscape@3.33.1: {}
+ cytoscape@3.33.2: {}
d3-array@2.12.1:
dependencies:
@@ -6672,7 +6737,7 @@ snapshots:
d3-delaunay@6.0.4:
dependencies:
- delaunator: 5.0.1
+ delaunator: 5.1.0
d3-dispatch@3.0.1: {}
@@ -6699,7 +6764,7 @@ snapshots:
d3-quadtree: 3.0.1
d3-timer: 3.0.1
- d3-format@3.1.0: {}
+ d3-format@3.1.2: {}
d3-geo@3.1.1:
dependencies:
@@ -6734,7 +6799,7 @@ snapshots:
d3-scale@4.0.2:
dependencies:
d3-array: 3.2.4
- d3-format: 3.1.0
+ d3-format: 3.1.2
d3-interpolate: 3.0.1
d3-time: 3.1.0
d3-time-format: 4.1.0
@@ -6791,7 +6856,7 @@ snapshots:
d3-ease: 3.0.1
d3-fetch: 3.0.1
d3-force: 3.0.0
- d3-format: 3.1.0
+ d3-format: 3.1.2
d3-geo: 3.1.1
d3-hierarchy: 3.1.2
d3-interpolate: 3.0.1
@@ -6809,17 +6874,17 @@ snapshots:
d3-transition: 3.0.1(d3-selection@3.0.0)
d3-zoom: 3.0.0
- dagre-d3-es@7.0.13:
+ dagre-d3-es@7.0.14:
dependencies:
d3: 7.9.0
- lodash-es: 4.17.22
+ lodash-es: 4.18.1
data-urls@5.0.0:
dependencies:
whatwg-mimetype: 4.0.0
whatwg-url: 14.2.0
- dayjs@1.11.19: {}
+ dayjs@1.11.20: {}
de-indent@1.0.2: {}
@@ -6831,7 +6896,7 @@ snapshots:
decimal.js@10.6.0: {}
- decode-named-character-reference@1.2.0:
+ decode-named-character-reference@1.3.0:
dependencies:
character-entities: 2.0.2
@@ -6841,9 +6906,9 @@ snapshots:
deep-is@0.1.4: {}
- delaunator@5.0.1:
+ delaunator@5.1.0:
dependencies:
- robust-predicates: 3.0.2
+ robust-predicates: 3.0.3
delayed-stream@1.0.0: {}
@@ -6871,6 +6936,10 @@ snapshots:
optionalDependencies:
'@types/trusted-types': 2.0.7
+ dompurify@3.3.3:
+ optionalDependencies:
+ '@types/trusted-types': 2.0.7
+
driver.js@1.4.0: {}
dunder-proto@1.0.1:
@@ -6923,7 +6992,7 @@ snapshots:
has-tostringtag: 1.0.2
hasown: 2.0.2
- es-toolkit@1.43.0: {}
+ es-toolkit@1.45.1: {}
esast-util-from-estree@2.0.0:
dependencies:
@@ -6935,7 +7004,7 @@ snapshots:
esast-util-from-js@2.0.1:
dependencies:
'@types/estree-jsx': 1.0.5
- acorn: 8.15.0
+ acorn: 8.16.0
esast-util-from-estree: 2.0.0
vfile-message: 4.0.3
@@ -7180,10 +7249,10 @@ snapshots:
fraction.js@5.3.4: {}
- framer-motion@12.23.26(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
+ framer-motion@12.38.0(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
dependencies:
- motion-dom: 12.23.23
- motion-utils: 12.23.6
+ motion-dom: 12.38.0
+ motion-utils: 12.36.0
tslib: 2.8.1
optionalDependencies:
'@emotion/is-prop-valid': 1.4.0
@@ -7199,7 +7268,7 @@ snapshots:
get-caller-file@2.0.5: {}
- get-east-asian-width@1.4.0: {}
+ get-east-asian-width@1.5.0: {}
get-intrinsic@1.3.0:
dependencies:
@@ -7334,7 +7403,7 @@ snapshots:
mdast-util-to-hast: 13.2.1
parse5: 7.3.0
unist-util-position: 5.0.0
- unist-util-visit: 5.0.0
+ unist-util-visit: 5.1.0
vfile: 6.0.3
web-namespaces: 2.0.1
zwitch: 2.0.4
@@ -7459,7 +7528,7 @@ snapshots:
ignore@5.3.2: {}
- immer@11.1.3: {}
+ immer@11.1.4: {}
import-fresh@3.3.1:
dependencies:
@@ -7625,7 +7694,7 @@ snapshots:
dependencies:
string-convert: 0.2.1
- katex@0.16.27:
+ katex@0.16.45:
dependencies:
commander: 8.3.0
@@ -7635,13 +7704,14 @@ snapshots:
khroma@2.1.0: {}
- langium@3.3.1:
+ langium@4.2.2:
dependencies:
- chevrotain: 11.0.3
- chevrotain-allstar: 0.3.1(chevrotain@11.0.3)
+ '@chevrotain/regexp-to-ast': 12.0.0
+ chevrotain: 12.0.0
+ chevrotain-allstar: 0.4.1(chevrotain@12.0.0)
vscode-languageserver: 9.0.1
vscode-languageserver-textdocument: 1.0.12
- vscode-uri: 3.0.8
+ vscode-uri: 3.1.0
layout-base@1.0.2: {}
@@ -7677,7 +7747,7 @@ snapshots:
lit-element@4.2.2:
dependencies:
- '@lit-labs/ssr-dom-shim': 1.5.0
+ '@lit-labs/ssr-dom-shim': 1.5.1
'@lit/reactive-element': 2.1.2
lit-html: 3.3.2
@@ -7699,14 +7769,14 @@ snapshots:
dependencies:
p-locate: 5.0.0
- lodash-es@4.17.21: {}
-
- lodash-es@4.17.22: {}
+ lodash-es@4.18.1: {}
lodash.merge@4.6.2: {}
lodash@4.17.21: {}
+ lodash@4.18.1: {}
+
longest-streak@3.1.0: {}
loose-envify@1.4.0:
@@ -7747,6 +7817,8 @@ snapshots:
marked@17.0.1: {}
+ marked@17.0.6: {}
+
math-intrinsics@1.1.0: {}
mdast-util-find-and-replace@3.0.2:
@@ -7756,11 +7828,11 @@ snapshots:
unist-util-is: 6.0.1
unist-util-visit-parents: 6.0.2
- mdast-util-from-markdown@2.0.2:
+ mdast-util-from-markdown@2.0.3:
dependencies:
'@types/mdast': 4.0.4
'@types/unist': 3.0.3
- decode-named-character-reference: 1.2.0
+ decode-named-character-reference: 1.3.0
devlop: 1.1.0
mdast-util-to-string: 4.0.0
micromark: 4.0.2
@@ -7785,7 +7857,7 @@ snapshots:
dependencies:
'@types/mdast': 4.0.4
devlop: 1.1.0
- mdast-util-from-markdown: 2.0.2
+ mdast-util-from-markdown: 2.0.3
mdast-util-to-markdown: 2.1.2
micromark-util-normalize-identifier: 2.0.1
transitivePeerDependencies:
@@ -7794,7 +7866,7 @@ snapshots:
mdast-util-gfm-strikethrough@2.0.0:
dependencies:
'@types/mdast': 4.0.4
- mdast-util-from-markdown: 2.0.2
+ mdast-util-from-markdown: 2.0.3
mdast-util-to-markdown: 2.1.2
transitivePeerDependencies:
- supports-color
@@ -7804,7 +7876,7 @@ snapshots:
'@types/mdast': 4.0.4
devlop: 1.1.0
markdown-table: 3.0.4
- mdast-util-from-markdown: 2.0.2
+ mdast-util-from-markdown: 2.0.3
mdast-util-to-markdown: 2.1.2
transitivePeerDependencies:
- supports-color
@@ -7813,14 +7885,14 @@ snapshots:
dependencies:
'@types/mdast': 4.0.4
devlop: 1.1.0
- mdast-util-from-markdown: 2.0.2
+ mdast-util-from-markdown: 2.0.3
mdast-util-to-markdown: 2.1.2
transitivePeerDependencies:
- supports-color
mdast-util-gfm@3.1.0:
dependencies:
- mdast-util-from-markdown: 2.0.2
+ mdast-util-from-markdown: 2.0.3
mdast-util-gfm-autolink-literal: 2.0.1
mdast-util-gfm-footnote: 2.1.0
mdast-util-gfm-strikethrough: 2.0.0
@@ -7836,7 +7908,7 @@ snapshots:
'@types/mdast': 4.0.4
devlop: 1.1.0
longest-streak: 3.1.0
- mdast-util-from-markdown: 2.0.2
+ mdast-util-from-markdown: 2.0.3
mdast-util-to-markdown: 2.1.2
unist-util-remove-position: 5.0.0
transitivePeerDependencies:
@@ -7848,7 +7920,7 @@ snapshots:
'@types/hast': 3.0.4
'@types/mdast': 4.0.4
devlop: 1.1.0
- mdast-util-from-markdown: 2.0.2
+ mdast-util-from-markdown: 2.0.3
mdast-util-to-markdown: 2.1.2
transitivePeerDependencies:
- supports-color
@@ -7861,7 +7933,7 @@ snapshots:
'@types/unist': 3.0.3
ccount: 2.0.1
devlop: 1.1.0
- mdast-util-from-markdown: 2.0.2
+ mdast-util-from-markdown: 2.0.3
mdast-util-to-markdown: 2.1.2
parse-entities: 4.0.2
stringify-entities: 4.0.4
@@ -7872,7 +7944,7 @@ snapshots:
mdast-util-mdx@3.0.0:
dependencies:
- mdast-util-from-markdown: 2.0.2
+ mdast-util-from-markdown: 2.0.3
mdast-util-mdx-expression: 2.0.1
mdast-util-mdx-jsx: 3.2.0
mdast-util-mdxjs-esm: 2.0.1
@@ -7886,7 +7958,7 @@ snapshots:
'@types/hast': 3.0.4
'@types/mdast': 4.0.4
devlop: 1.1.0
- mdast-util-from-markdown: 2.0.2
+ mdast-util-from-markdown: 2.0.3
mdast-util-to-markdown: 2.1.2
transitivePeerDependencies:
- supports-color
@@ -7910,7 +7982,7 @@ snapshots:
micromark-util-sanitize-uri: 2.0.1
trim-lines: 3.0.1
unist-util-position: 5.0.0
- unist-util-visit: 5.0.0
+ unist-util-visit: 5.1.0
vfile: 6.0.3
mdast-util-to-markdown@2.1.2:
@@ -7922,7 +7994,7 @@ snapshots:
mdast-util-to-string: 4.0.0
micromark-util-classify-character: 2.0.1
micromark-util-decode-string: 2.0.1
- unist-util-visit: 5.0.0
+ unist-util-visit: 5.1.0
zwitch: 2.0.4
mdast-util-to-string@4.0.0:
@@ -7938,23 +8010,24 @@ snapshots:
merge2@1.4.1: {}
- mermaid@11.12.2:
+ mermaid@11.14.0:
dependencies:
- '@braintree/sanitize-url': 7.1.1
+ '@braintree/sanitize-url': 7.1.2
'@iconify/utils': 3.1.0
- '@mermaid-js/parser': 0.6.3
+ '@mermaid-js/parser': 1.1.0
'@types/d3': 7.4.3
- cytoscape: 3.33.1
- cytoscape-cose-bilkent: 4.1.0(cytoscape@3.33.1)
- cytoscape-fcose: 2.2.0(cytoscape@3.33.1)
+ '@upsetjs/venn.js': 2.0.0
+ cytoscape: 3.33.2
+ cytoscape-cose-bilkent: 4.1.0(cytoscape@3.33.2)
+ cytoscape-fcose: 2.2.0(cytoscape@3.33.2)
d3: 7.9.0
d3-sankey: 0.12.3
- dagre-d3-es: 7.0.13
- dayjs: 1.11.19
- dompurify: 3.3.1
- katex: 0.16.27
+ dagre-d3-es: 7.0.14
+ dayjs: 1.11.20
+ dompurify: 3.3.3
+ katex: 0.16.45
khroma: 2.1.0
- lodash-es: 4.17.22
+ lodash-es: 4.18.1
marked: 16.4.2
roughjs: 4.6.6
stylis: 4.3.6
@@ -7963,7 +8036,7 @@ snapshots:
micromark-core-commonmark@2.0.3:
dependencies:
- decode-named-character-reference: 1.2.0
+ decode-named-character-reference: 1.3.0
devlop: 1.1.0
micromark-factory-destination: 2.0.1
micromark-factory-label: 2.0.1
@@ -7982,7 +8055,7 @@ snapshots:
micromark-extension-cjk-friendly-util@2.1.1(micromark-util-types@2.0.2):
dependencies:
- get-east-asian-width: 1.4.0
+ get-east-asian-width: 1.5.0
micromark-util-character: 2.1.1
micromark-util-symbol: 2.0.1
optionalDependencies:
@@ -8059,9 +8132,9 @@ snapshots:
micromark-extension-math@3.1.0:
dependencies:
- '@types/katex': 0.16.7
+ '@types/katex': 0.16.8
devlop: 1.1.0
- katex: 0.16.27
+ katex: 0.16.45
micromark-factory-space: 2.0.1
micromark-util-character: 2.1.1
micromark-util-symbol: 2.0.1
@@ -8109,8 +8182,8 @@ snapshots:
micromark-extension-mdxjs@3.0.0:
dependencies:
- acorn: 8.15.0
- acorn-jsx: 5.3.2(acorn@8.15.0)
+ acorn: 8.16.0
+ acorn-jsx: 5.3.2(acorn@8.16.0)
micromark-extension-mdx-expression: 3.0.1
micromark-extension-mdx-jsx: 3.0.2
micromark-extension-mdx-md: 2.0.0
@@ -8188,7 +8261,7 @@ snapshots:
micromark-util-decode-string@2.0.1:
dependencies:
- decode-named-character-reference: 1.2.0
+ decode-named-character-reference: 1.3.0
micromark-util-character: 2.1.1
micromark-util-decode-numeric-character-reference: 2.0.2
micromark-util-symbol: 2.0.1
@@ -8234,9 +8307,9 @@ snapshots:
micromark@4.0.2:
dependencies:
- '@types/debug': 4.1.12
+ '@types/debug': 4.1.13
debug: 4.4.3
- decode-named-character-reference: 1.2.0
+ decode-named-character-reference: 1.3.0
devlop: 1.1.0
micromark-core-commonmark: 2.0.3
micromark-factory-space: 2.0.1
@@ -8284,22 +8357,22 @@ snapshots:
for-in: 1.0.2
is-extendable: 1.0.1
- mlly@1.8.0:
+ mlly@1.8.2:
dependencies:
- acorn: 8.15.0
+ acorn: 8.16.0
pathe: 2.0.3
pkg-types: 1.3.1
- ufo: 1.6.1
+ ufo: 1.6.3
- motion-dom@12.23.23:
+ motion-dom@12.38.0:
dependencies:
- motion-utils: 12.23.6
+ motion-utils: 12.36.0
- motion-utils@12.23.6: {}
+ motion-utils@12.36.0: {}
motion@12.23.26(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
dependencies:
- framer-motion: 12.23.26(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ framer-motion: 12.38.0(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
tslib: 2.8.1
optionalDependencies:
'@emotion/is-prop-valid': 1.4.0
@@ -8353,7 +8426,7 @@ snapshots:
oniguruma-parser@0.12.1: {}
- oniguruma-to-es@4.3.4:
+ oniguruma-to-es@4.3.5:
dependencies:
oniguruma-parser: 0.12.1
regex: 6.1.0
@@ -8399,7 +8472,7 @@ snapshots:
'@types/unist': 2.0.11
character-entities-legacy: 3.0.0
character-reference-invalid: 2.0.1
- decode-named-character-reference: 1.2.0
+ decode-named-character-reference: 1.3.0
is-alphanumerical: 2.0.1
is-decimal: 2.0.1
is-hexadecimal: 2.0.1
@@ -8465,7 +8538,7 @@ snapshots:
pkg-types@1.3.1:
dependencies:
confbox: 0.1.8
- mlly: 1.8.0
+ mlly: 1.8.2
pathe: 2.0.3
pngjs@5.0.0: {}
@@ -8530,7 +8603,7 @@ snapshots:
proto-list@1.2.4: {}
- proxy-from-env@1.1.0: {}
+ proxy-from-env@2.1.0: {}
psl@1.15.0:
dependencies:
@@ -8556,7 +8629,7 @@ snapshots:
rc-collapse@4.0.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
dependencies:
- '@babel/runtime': 7.28.4
+ '@babel/runtime': 7.29.2
classnames: 2.5.1
rc-motion: 2.9.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
rc-util: 5.44.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
@@ -8565,7 +8638,7 @@ snapshots:
rc-dialog@9.6.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
dependencies:
- '@babel/runtime': 7.28.4
+ '@babel/runtime': 7.29.2
'@rc-component/portal': 1.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
classnames: 2.5.1
rc-motion: 2.9.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
@@ -8575,14 +8648,14 @@ snapshots:
rc-footer@0.6.8(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
dependencies:
- '@babel/runtime': 7.28.4
+ '@babel/runtime': 7.29.2
classnames: 2.5.1
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
rc-image@7.12.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
dependencies:
- '@babel/runtime': 7.28.4
+ '@babel/runtime': 7.29.2
'@rc-component/portal': 1.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
classnames: 2.5.1
rc-dialog: 9.6.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
@@ -8593,8 +8666,8 @@ snapshots:
rc-input-number@9.5.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
dependencies:
- '@babel/runtime': 7.28.4
- '@rc-component/mini-decimal': 1.1.0
+ '@babel/runtime': 7.29.2
+ '@rc-component/mini-decimal': 1.1.3
classnames: 2.5.1
rc-input: 1.8.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
rc-util: 5.44.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
@@ -8603,7 +8676,7 @@ snapshots:
rc-input@1.8.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
dependencies:
- '@babel/runtime': 7.28.4
+ '@babel/runtime': 7.29.2
classnames: 2.5.1
rc-util: 5.44.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
react: 19.2.3
@@ -8611,8 +8684,8 @@ snapshots:
rc-menu@9.16.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
dependencies:
- '@babel/runtime': 7.28.4
- '@rc-component/trigger': 2.3.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@babel/runtime': 7.29.2
+ '@rc-component/trigger': 2.3.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
classnames: 2.5.1
rc-motion: 2.9.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
rc-overflow: 1.5.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
@@ -8622,7 +8695,7 @@ snapshots:
rc-motion@2.9.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
dependencies:
- '@babel/runtime': 7.28.4
+ '@babel/runtime': 7.29.2
classnames: 2.5.1
rc-util: 5.44.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
react: 19.2.3
@@ -8630,7 +8703,7 @@ snapshots:
rc-overflow@1.5.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
dependencies:
- '@babel/runtime': 7.28.4
+ '@babel/runtime': 7.29.2
classnames: 2.5.1
rc-resize-observer: 1.4.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
rc-util: 5.44.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
@@ -8639,7 +8712,7 @@ snapshots:
rc-resize-observer@1.4.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
dependencies:
- '@babel/runtime': 7.28.4
+ '@babel/runtime': 7.29.2
classnames: 2.5.1
rc-util: 5.44.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
react: 19.2.3
@@ -8648,7 +8721,7 @@ snapshots:
rc-util@5.44.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
dependencies:
- '@babel/runtime': 7.28.4
+ '@babel/runtime': 7.29.2
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
react-is: 18.3.1
@@ -8673,9 +8746,9 @@ snapshots:
react: 19.2.3
scheduler: 0.27.0
- react-draggable@4.4.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
+ react-draggable@4.5.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
dependencies:
- clsx: 1.2.1
+ clsx: 2.1.1
prop-types: 15.8.1
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
@@ -8687,14 +8760,13 @@ snapshots:
prop-types: 15.8.1
react: 19.2.3
- react-error-boundary@6.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
+ react-error-boundary@6.1.1(react@19.2.3):
dependencies:
react: 19.2.3
- react-dom: 19.2.3(react@19.2.3)
react-fast-compare@3.2.2: {}
- react-hotkeys-hook@5.2.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
+ react-hotkeys-hook@5.2.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
dependencies:
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
@@ -8716,7 +8788,7 @@ snapshots:
remark-parse: 11.0.0
remark-rehype: 11.1.2
unified: 11.0.5
- unist-util-visit: 5.0.0
+ unist-util-visit: 5.1.0
vfile: 6.0.3
transitivePeerDependencies:
- supports-color
@@ -8725,12 +8797,12 @@ snapshots:
optionalDependencies:
react: 19.2.3
- react-rnd@10.5.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
+ react-rnd@10.5.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
dependencies:
re-resizable: 6.11.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
- react-draggable: 4.4.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ react-draggable: 4.5.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
tslib: 2.6.2
react-zoom-pan-pinch@3.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
@@ -8756,10 +8828,10 @@ snapshots:
estree-util-build-jsx: 3.0.1
vfile: 6.0.3
- recma-jsx@1.0.1(acorn@8.15.0):
+ recma-jsx@1.0.1(acorn@8.16.0):
dependencies:
- acorn: 8.15.0
- acorn-jsx: 5.3.2(acorn@8.15.0)
+ acorn: 8.16.0
+ acorn-jsx: 5.3.2(acorn@8.16.0)
estree-util-to-js: 2.0.0
recma-parse: 1.0.0
recma-stringify: 1.0.0
@@ -8791,18 +8863,18 @@ snapshots:
rehype-github-alerts@4.2.0:
dependencies:
- '@primer/octicons': 19.21.1
+ '@primer/octicons': 19.23.1
hast-util-from-html: 2.0.3
hast-util-is-element: 3.0.0
- unist-util-visit: 5.0.0
+ unist-util-visit: 5.1.0
rehype-katex@7.0.1:
dependencies:
'@types/hast': 3.0.4
- '@types/katex': 0.16.7
+ '@types/katex': 0.16.8
hast-util-from-html-isomorphic: 2.0.0
hast-util-to-text: 4.0.2
- katex: 0.16.27
+ katex: 0.16.45
unist-util-visit-parents: 6.0.2
vfile: 6.0.3
@@ -8853,7 +8925,7 @@ snapshots:
mdast-util-find-and-replace: 3.0.2
mdast-util-to-string: 4.0.0
to-vfile: 8.0.0
- unist-util-visit: 5.0.0
+ unist-util-visit: 5.1.0
vfile: 6.0.3
remark-math@6.0.0:
@@ -8875,7 +8947,7 @@ snapshots:
remark-parse@11.0.0:
dependencies:
'@types/mdast': 4.0.4
- mdast-util-from-markdown: 2.0.2
+ mdast-util-from-markdown: 2.0.3
micromark-util-types: 2.0.2
unified: 11.0.5
transitivePeerDependencies:
@@ -8919,7 +8991,7 @@ snapshots:
dependencies:
glob: 7.2.3
- robust-predicates@3.0.2: {}
+ robust-predicates@3.0.3: {}
rollup@4.54.0:
dependencies:
@@ -8999,21 +9071,21 @@ snapshots:
shebang-regex@3.0.0: {}
- shiki-stream@0.1.3(react@19.2.3)(vue@3.5.26(typescript@5.6.3)):
+ shiki-stream@0.1.4(react@19.2.3)(vue@3.5.26(typescript@5.6.3)):
dependencies:
- '@shikijs/core': 3.20.0
+ '@shikijs/core': 3.23.0
optionalDependencies:
react: 19.2.3
vue: 3.5.26(typescript@5.6.3)
- shiki@3.20.0:
+ shiki@3.23.0:
dependencies:
- '@shikijs/core': 3.20.0
- '@shikijs/engine-javascript': 3.20.0
- '@shikijs/engine-oniguruma': 3.20.0
- '@shikijs/langs': 3.20.0
- '@shikijs/themes': 3.20.0
- '@shikijs/types': 3.20.0
+ '@shikijs/core': 3.23.0
+ '@shikijs/engine-javascript': 3.23.0
+ '@shikijs/engine-oniguruma': 3.23.0
+ '@shikijs/langs': 3.23.0
+ '@shikijs/themes': 3.23.0
+ '@shikijs/types': 3.23.0
'@shikijs/vscode-textmate': 10.0.2
'@types/hast': 3.0.4
@@ -9102,7 +9174,7 @@ snapshots:
supports-preserve-symlinks-flag@1.0.0: {}
- swr@2.3.8(react@19.2.3):
+ swr@2.4.1(react@19.2.3):
dependencies:
dequal: 2.0.3
react: 19.2.3
@@ -9164,7 +9236,7 @@ snapshots:
tinyexec@0.3.2: {}
- tinyexec@1.0.2: {}
+ tinyexec@1.1.1: {}
tinyglobby@0.2.15:
dependencies:
@@ -9222,7 +9294,7 @@ snapshots:
typescript@5.6.3: {}
- ufo@1.6.1: {}
+ ufo@1.6.3: {}
undici-types@6.21.0: {}
@@ -9258,7 +9330,7 @@ snapshots:
unist-util-remove-position@5.0.0:
dependencies:
'@types/unist': 3.0.3
- unist-util-visit: 5.0.0
+ unist-util-visit: 5.1.0
unist-util-stringify-position@4.0.0:
dependencies:
@@ -9269,7 +9341,7 @@ snapshots:
'@types/unist': 3.0.3
unist-util-is: 6.0.1
- unist-util-visit@5.0.0:
+ unist-util-visit@5.1.0:
dependencies:
'@types/unist': 3.0.3
unist-util-is: 6.0.1
@@ -9421,8 +9493,6 @@ snapshots:
dependencies:
vscode-languageserver-protocol: 3.17.5
- vscode-uri@3.0.8: {}
-
vscode-uri@3.1.0: {}
vue-chartjs@5.3.3(chart.js@4.5.1)(vue@3.5.26(typescript@5.6.3)):
diff --git a/frontend/src/api/admin/accounts.ts b/frontend/src/api/admin/accounts.ts
index 8e40e18b33db6a339e82ecfdd49024d45b7dabf2..a146f1f708c700f0dfe7934e994536fec0ef7eff 100644
--- a/frontend/src/api/admin/accounts.ts
+++ b/frontend/src/api/admin/accounts.ts
@@ -38,6 +38,8 @@ export async function list(
search?: string
privacy_mode?: string
lite?: string
+ sort_by?: string
+ sort_order?: 'asc' | 'desc'
},
options?: {
signal?: AbortSignal
@@ -71,6 +73,8 @@ export async function listWithEtag(
search?: string
privacy_mode?: string
lite?: string
+ sort_by?: string
+ sort_order?: 'asc' | 'desc'
},
options?: {
signal?: AbortSignal
@@ -500,7 +504,11 @@ export async function exportData(options?: {
platform?: string
type?: string
status?: string
+ group?: string
+ privacy_mode?: string
search?: string
+ sort_by?: string
+ sort_order?: 'asc' | 'desc'
}
includeProxies?: boolean
}): Promise {
@@ -508,11 +516,15 @@ export async function exportData(options?: {
if (options?.ids && options.ids.length > 0) {
params.ids = options.ids.join(',')
} else if (options?.filters) {
- const { platform, type, status, search } = options.filters
+ const { platform, type, status, group, privacy_mode, search, sort_by, sort_order } = options.filters
if (platform) params.platform = platform
if (type) params.type = type
if (status) params.status = status
+ if (group) params.group = group
+ if (privacy_mode) params.privacy_mode = privacy_mode
if (search) params.search = search
+ if (sort_by) params.sort_by = sort_by
+ if (sort_order) params.sort_order = sort_order
}
if (options?.includeProxies === false) {
params.include_proxies = 'false'
diff --git a/frontend/src/api/admin/announcements.ts b/frontend/src/api/admin/announcements.ts
index d02fdda7720b6c56675f9f37b058848f37a97403..92392a675348c2856588fdbb13f98f414e4de952 100644
--- a/frontend/src/api/admin/announcements.ts
+++ b/frontend/src/api/admin/announcements.ts
@@ -17,10 +17,16 @@ export async function list(
filters?: {
status?: string
search?: string
+ sort_by?: string
+ sort_order?: 'asc' | 'desc'
+ },
+ options?: {
+ signal?: AbortSignal
}
): Promise> {
const { data } = await apiClient.get>('/admin/announcements', {
- params: { page, page_size: pageSize, ...filters }
+ params: { page, page_size: pageSize, ...filters },
+ signal: options?.signal
})
return data
}
@@ -49,11 +55,21 @@ export async function getReadStatus(
id: number,
page: number = 1,
pageSize: number = 20,
- search: string = ''
+ filters?: {
+ search?: string
+ sort_by?: string
+ sort_order?: 'asc' | 'desc'
+ },
+ options?: {
+ signal?: AbortSignal
+ }
): Promise> {
const { data } = await apiClient.get>(
`/admin/announcements/${id}/read-status`,
- { params: { page, page_size: pageSize, search } }
+ {
+ params: { page, page_size: pageSize, ...filters },
+ signal: options?.signal
+ }
)
return data
}
@@ -68,4 +84,3 @@ const announcementsAPI = {
}
export default announcementsAPI
-
diff --git a/frontend/src/api/admin/channels.ts b/frontend/src/api/admin/channels.ts
index 5334dd473d9fac4d2540371c0ce78b5749e741e8..b34550222b832bf1a3c9eb28418e082fc363c563 100644
--- a/frontend/src/api/admin/channels.ts
+++ b/frontend/src/api/admin/channels.ts
@@ -83,6 +83,8 @@ export async function list(
filters?: {
status?: string
search?: string
+ sort_by?: string
+ sort_order?: 'asc' | 'desc'
},
options?: { signal?: AbortSignal }
): Promise> {
diff --git a/frontend/src/api/admin/groups.ts b/frontend/src/api/admin/groups.ts
index 5885dc6ad7e9c0265610702a79121f2eca9bc1a3..8739d5cbe9a8b74f4e0af1f61ec8b30aa33fb21f 100644
--- a/frontend/src/api/admin/groups.ts
+++ b/frontend/src/api/admin/groups.ts
@@ -27,6 +27,8 @@ export async function list(
status?: 'active' | 'inactive'
is_exclusive?: boolean
search?: string
+ sort_by?: string
+ sort_order?: 'asc' | 'desc'
},
options?: {
signal?: AbortSignal
diff --git a/frontend/src/api/admin/index.ts b/frontend/src/api/admin/index.ts
index da1e3cfa8f90f386f029e6877ffd2f379e7f1ee6..72597365434ff09687adc59155464e103fc0221f 100644
--- a/frontend/src/api/admin/index.ts
+++ b/frontend/src/api/admin/index.ts
@@ -26,6 +26,7 @@ import scheduledTestsAPI from './scheduledTests'
import backupAPI from './backup'
import tlsFingerprintProfileAPI from './tlsFingerprintProfile'
import channelsAPI from './channels'
+import adminPaymentAPI from './payment'
/**
* Unified admin API object for convenient access
@@ -53,7 +54,8 @@ export const adminAPI = {
scheduledTests: scheduledTestsAPI,
backup: backupAPI,
tlsFingerprintProfiles: tlsFingerprintProfileAPI,
- channels: channelsAPI
+ channels: channelsAPI,
+ payment: adminPaymentAPI
}
export {
@@ -79,7 +81,8 @@ export {
scheduledTestsAPI,
backupAPI,
tlsFingerprintProfileAPI,
- channelsAPI
+ channelsAPI,
+ adminPaymentAPI
}
export default adminAPI
diff --git a/frontend/src/api/admin/payment.ts b/frontend/src/api/admin/payment.ts
new file mode 100644
index 0000000000000000000000000000000000000000..946c91b07e784cb1f0c4f544bda53a8b0fc04897
--- /dev/null
+++ b/frontend/src/api/admin/payment.ts
@@ -0,0 +1,176 @@
+/**
+ * Admin Payment API endpoints
+ * Handles payment management operations for administrators
+ */
+
+import { apiClient } from '../client'
+import type {
+ DashboardStats,
+ PaymentOrder,
+ PaymentChannel,
+ SubscriptionPlan,
+ ProviderInstance
+} from '@/types/payment'
+import type { BasePaginationResponse } from '@/types'
+
+/** Admin-facing payment config returned by GET /admin/payment/config */
+export interface AdminPaymentConfig {
+ enabled: boolean
+ min_amount: number
+ max_amount: number
+ daily_limit: number
+ order_timeout_minutes: number
+ max_pending_orders: number
+ enabled_payment_types: string[]
+ balance_disabled: boolean
+ load_balance_strategy: string
+ product_name_prefix: string
+ product_name_suffix: string
+ help_image_url: string
+ help_text: string
+}
+
+/** Fields accepted by PUT /admin/payment/config (all optional via pointer semantics) */
+export interface UpdatePaymentConfigRequest {
+ enabled?: boolean
+ min_amount?: number
+ max_amount?: number
+ daily_limit?: number
+ order_timeout_minutes?: number
+ max_pending_orders?: number
+ enabled_payment_types?: string[]
+ balance_disabled?: boolean
+ load_balance_strategy?: string
+ product_name_prefix?: string
+ product_name_suffix?: string
+ help_image_url?: string
+ help_text?: string
+}
+
+export const adminPaymentAPI = {
+ // ==================== Config ====================
+
+ /** Get payment configuration (admin view) */
+ getConfig() {
+ return apiClient.get('/admin/payment/config')
+ },
+
+ /** Update payment configuration */
+ updateConfig(data: UpdatePaymentConfigRequest) {
+ return apiClient.put('/admin/payment/config', data)
+ },
+
+ // ==================== Dashboard ====================
+
+ /** Get payment dashboard statistics */
+ getDashboard(days?: number) {
+ return apiClient.get('/admin/payment/dashboard', {
+ params: days ? { days } : undefined
+ })
+ },
+
+ // ==================== Orders ====================
+
+ /** Get all orders (paginated, with filters) */
+ getOrders(params?: {
+ page?: number
+ page_size?: number
+ status?: string
+ payment_type?: string
+ user_id?: number
+ keyword?: string
+ start_date?: string
+ end_date?: string
+ order_type?: string
+ }) {
+ return apiClient.get>('/admin/payment/orders', { params })
+ },
+
+ /** Get a specific order by ID */
+ getOrder(id: number) {
+ return apiClient.get(`/admin/payment/orders/${id}`)
+ },
+
+ /** Cancel an order (admin) */
+ cancelOrder(id: number) {
+ return apiClient.post(`/admin/payment/orders/${id}/cancel`)
+ },
+
+ /** Retry recharge for a failed order */
+ retryRecharge(id: number) {
+ return apiClient.post(`/admin/payment/orders/${id}/retry`)
+ },
+
+ /** Process a refund */
+ refundOrder(id: number, data: { amount: number; reason: string; deduct_balance?: boolean; force?: boolean }) {
+ return apiClient.post(`/admin/payment/orders/${id}/refund`, data)
+ },
+
+ // ==================== Channels ====================
+
+ /** Get all payment channels */
+ getChannels() {
+ return apiClient.get('/admin/payment/channels')
+ },
+
+ /** Create a payment channel */
+ createChannel(data: Partial) {
+ return apiClient.post('/admin/payment/channels', data)
+ },
+
+ /** Update a payment channel */
+ updateChannel(id: number, data: Partial) {
+ return apiClient.put(`/admin/payment/channels/${id}`, data)
+ },
+
+ /** Delete a payment channel */
+ deleteChannel(id: number) {
+ return apiClient.delete(`/admin/payment/channels/${id}`)
+ },
+
+ // ==================== Subscription Plans ====================
+
+ /** Get all subscription plans */
+ getPlans() {
+ return apiClient.get('/admin/payment/plans')
+ },
+
+ /** Create a subscription plan */
+ createPlan(data: Record) {
+ return apiClient.post('/admin/payment/plans', data)
+ },
+
+ /** Update a subscription plan */
+ updatePlan(id: number, data: Record) {
+ return apiClient.put(`/admin/payment/plans/${id}`, data)
+ },
+
+ /** Delete a subscription plan */
+ deletePlan(id: number) {
+ return apiClient.delete(`/admin/payment/plans/${id}`)
+ },
+
+ // ==================== Provider Instances ====================
+
+ /** Get all provider instances */
+ getProviders() {
+ return apiClient.get('/admin/payment/providers')
+ },
+
+ /** Create a provider instance */
+ createProvider(data: Partial) {
+ return apiClient.post('/admin/payment/providers', data)
+ },
+
+ /** Update a provider instance */
+ updateProvider(id: number, data: Partial) {
+ return apiClient.put(`/admin/payment/providers/${id}`, data)
+ },
+
+ /** Delete a provider instance */
+ deleteProvider(id: number) {
+ return apiClient.delete(`/admin/payment/providers/${id}`)
+ }
+}
+
+export default adminPaymentAPI
diff --git a/frontend/src/api/admin/promo.ts b/frontend/src/api/admin/promo.ts
index 6a8c4559e939766b8b7cd435cc51708f7b03f0ca..b24dffc2003dc0a2cd441119c551fbd173a0fa6e 100644
--- a/frontend/src/api/admin/promo.ts
+++ b/frontend/src/api/admin/promo.ts
@@ -17,10 +17,16 @@ export async function list(
filters?: {
status?: string
search?: string
+ sort_by?: string
+ sort_order?: 'asc' | 'desc'
+ },
+ options?: {
+ signal?: AbortSignal
}
): Promise> {
const { data } = await apiClient.get>('/admin/promo-codes', {
- params: { page, page_size: pageSize, ...filters }
+ params: { page, page_size: pageSize, ...filters },
+ signal: options?.signal
})
return data
}
diff --git a/frontend/src/api/admin/proxies.ts b/frontend/src/api/admin/proxies.ts
index 5e31ae20f75a29b302783026975661eee899b1fc..3e041ba9a5d134541fe86ed557e1c63f57cd5f5b 100644
--- a/frontend/src/api/admin/proxies.ts
+++ b/frontend/src/api/admin/proxies.ts
@@ -29,6 +29,8 @@ export async function list(
protocol?: string
status?: 'active' | 'inactive'
search?: string
+ sort_by?: string
+ sort_order?: 'asc' | 'desc'
},
options?: {
signal?: AbortSignal
@@ -227,16 +229,20 @@ export async function exportData(options?: {
protocol?: string
status?: 'active' | 'inactive'
search?: string
+ sort_by?: string
+ sort_order?: 'asc' | 'desc'
}
}): Promise {
const params: Record = {}
if (options?.ids && options.ids.length > 0) {
params.ids = options.ids.join(',')
} else if (options?.filters) {
- const { protocol, status, search } = options.filters
+ const { protocol, status, search, sort_by, sort_order } = options.filters
if (protocol) params.protocol = protocol
if (status) params.status = status
if (search) params.search = search
+ if (sort_by) params.sort_by = sort_by
+ if (sort_order) params.sort_order = sort_order
}
const { data } = await apiClient.get('/admin/proxies/data', { params })
return data
diff --git a/frontend/src/api/admin/redeem.ts b/frontend/src/api/admin/redeem.ts
index a53c3566bbf261bda3eb3cabc4fe02e4deb0375a..57626b1efd9fe0d655cd60b54e0356d86198d2fc 100644
--- a/frontend/src/api/admin/redeem.ts
+++ b/frontend/src/api/admin/redeem.ts
@@ -25,6 +25,8 @@ export async function list(
type?: RedeemCodeType
status?: 'active' | 'used' | 'expired' | 'unused'
search?: string
+ sort_by?: string
+ sort_order?: 'asc' | 'desc'
},
options?: {
signal?: AbortSignal
@@ -151,7 +153,10 @@ export async function getStats(): Promise<{
*/
export async function exportCodes(filters?: {
type?: RedeemCodeType
- status?: 'active' | 'used' | 'expired'
+ status?: 'used' | 'expired' | 'unused'
+ search?: string
+ sort_by?: string
+ sort_order?: 'asc' | 'desc'
}): Promise {
const response = await apiClient.get('/admin/redeem-codes/export', {
params: filters,
diff --git a/frontend/src/api/admin/settings.ts b/frontend/src/api/admin/settings.ts
index b7ee6be5cebc6533088cfa62486450931714567c..504abe9c9b07fa79828a70558292f8201189a634 100644
--- a/frontend/src/api/admin/settings.ts
+++ b/frontend/src/api/admin/settings.ts
@@ -38,8 +38,8 @@ export interface SystemSettings {
doc_url: string
home_content: string
hide_ccs_import_button: boolean
- purchase_subscription_enabled: boolean
- purchase_subscription_url: string
+ table_default_page_size: number
+ table_page_size_options: number[]
backend_mode_enabled: boolean
custom_menu_items: CustomMenuItem[]
custom_endpoints: CustomEndpoint[]
@@ -62,6 +62,30 @@ export interface SystemSettings {
linuxdo_connect_client_secret_configured: boolean
linuxdo_connect_redirect_url: string
+ // Generic OIDC OAuth settings
+ oidc_connect_enabled: boolean
+ oidc_connect_provider_name: string
+ oidc_connect_client_id: string
+ oidc_connect_client_secret_configured: boolean
+ oidc_connect_issuer_url: string
+ oidc_connect_discovery_url: string
+ oidc_connect_authorize_url: string
+ oidc_connect_token_url: string
+ oidc_connect_userinfo_url: string
+ oidc_connect_jwks_url: string
+ oidc_connect_scopes: string
+ oidc_connect_redirect_url: string
+ oidc_connect_frontend_redirect_url: string
+ oidc_connect_token_auth_method: string
+ oidc_connect_use_pkce: boolean
+ oidc_connect_validate_id_token: boolean
+ oidc_connect_allowed_signing_algs: string
+ oidc_connect_clock_skew_seconds: number
+ oidc_connect_require_email_verified: boolean
+ oidc_connect_userinfo_email_path: string
+ oidc_connect_userinfo_id_path: string
+ oidc_connect_userinfo_username_path: string
+
// Model fallback configuration
enable_model_fallback: boolean
fallback_model_anthropic: string
@@ -90,6 +114,26 @@ export interface SystemSettings {
enable_fingerprint_unification: boolean
enable_metadata_passthrough: boolean
enable_cch_signing: boolean
+
+ // Payment configuration
+ payment_enabled: boolean
+ payment_min_amount: number
+ payment_max_amount: number
+ payment_daily_limit: number
+ payment_order_timeout_minutes: number
+ payment_max_pending_orders: number
+ payment_enabled_types: string[]
+ payment_balance_disabled: boolean
+ payment_load_balance_strategy: string
+ payment_product_name_prefix: string
+ payment_product_name_suffix: string
+ payment_help_image_url: string
+ payment_help_text: string
+ payment_cancel_rate_limit_enabled: boolean
+ payment_cancel_rate_limit_max: number
+ payment_cancel_rate_limit_window: number
+ payment_cancel_rate_limit_unit: string
+ payment_cancel_rate_limit_window_mode: string
}
export interface UpdateSettingsRequest {
@@ -112,8 +156,8 @@ export interface UpdateSettingsRequest {
doc_url?: string
home_content?: string
hide_ccs_import_button?: boolean
- purchase_subscription_enabled?: boolean
- purchase_subscription_url?: string
+ table_default_page_size?: number
+ table_page_size_options?: number[]
backend_mode_enabled?: boolean
custom_menu_items?: CustomMenuItem[]
custom_endpoints?: CustomEndpoint[]
@@ -131,6 +175,28 @@ export interface UpdateSettingsRequest {
linuxdo_connect_client_id?: string
linuxdo_connect_client_secret?: string
linuxdo_connect_redirect_url?: string
+ oidc_connect_enabled?: boolean
+ oidc_connect_provider_name?: string
+ oidc_connect_client_id?: string
+ oidc_connect_client_secret?: string
+ oidc_connect_issuer_url?: string
+ oidc_connect_discovery_url?: string
+ oidc_connect_authorize_url?: string
+ oidc_connect_token_url?: string
+ oidc_connect_userinfo_url?: string
+ oidc_connect_jwks_url?: string
+ oidc_connect_scopes?: string
+ oidc_connect_redirect_url?: string
+ oidc_connect_frontend_redirect_url?: string
+ oidc_connect_token_auth_method?: string
+ oidc_connect_use_pkce?: boolean
+ oidc_connect_validate_id_token?: boolean
+ oidc_connect_allowed_signing_algs?: string
+ oidc_connect_clock_skew_seconds?: number
+ oidc_connect_require_email_verified?: boolean
+ oidc_connect_userinfo_email_path?: string
+ oidc_connect_userinfo_id_path?: string
+ oidc_connect_userinfo_username_path?: string
enable_model_fallback?: boolean
fallback_model_anthropic?: string
fallback_model_openai?: string
@@ -148,6 +214,25 @@ export interface UpdateSettingsRequest {
enable_fingerprint_unification?: boolean
enable_metadata_passthrough?: boolean
enable_cch_signing?: boolean
+ // Payment configuration
+ payment_enabled?: boolean
+ payment_min_amount?: number
+ payment_max_amount?: number
+ payment_daily_limit?: number
+ payment_order_timeout_minutes?: number
+ payment_max_pending_orders?: number
+ payment_enabled_types?: string[]
+ payment_balance_disabled?: boolean
+ payment_load_balance_strategy?: string
+ payment_product_name_prefix?: string
+ payment_product_name_suffix?: string
+ payment_help_image_url?: string
+ payment_help_text?: string
+ payment_cancel_rate_limit_enabled?: boolean
+ payment_cancel_rate_limit_max?: number
+ payment_cancel_rate_limit_window?: number
+ payment_cancel_rate_limit_unit?: string
+ payment_cancel_rate_limit_window_mode?: string
}
/**
diff --git a/frontend/src/api/admin/usage.ts b/frontend/src/api/admin/usage.ts
index d21b28dcdb830714c63f92fcf5f396fc4e77e6a0..37df7553ff29f08e9edf2dd4b6fb8a7b7e48b659 100644
--- a/frontend/src/api/admin/usage.ts
+++ b/frontend/src/api/admin/usage.ts
@@ -81,6 +81,8 @@ export interface AdminUsageQueryParams extends UsageQueryParams {
user_id?: number
exact_total?: boolean
billing_mode?: string
+ sort_by?: string
+ sort_order?: 'asc' | 'desc'
}
// ==================== API Functions ====================
diff --git a/frontend/src/api/admin/users.ts b/frontend/src/api/admin/users.ts
index bbf0ab512015d070ef8467cfb8733c80d3ac8c4f..39cb1dfa69217d7492e592d955cc5d7f2eb2aa63 100644
--- a/frontend/src/api/admin/users.ts
+++ b/frontend/src/api/admin/users.ts
@@ -24,6 +24,8 @@ export async function list(
group_name?: string // fuzzy filter by allowed group name
attributes?: Record // attributeId -> value
include_subscriptions?: boolean
+ sort_by?: string
+ sort_order?: 'asc' | 'desc'
},
options?: {
signal?: AbortSignal
@@ -37,7 +39,9 @@ export async function list(
role: filters?.role,
search: filters?.search,
group_name: filters?.group_name,
- include_subscriptions: filters?.include_subscriptions
+ include_subscriptions: filters?.include_subscriptions,
+ sort_by: filters?.sort_by,
+ sort_order: filters?.sort_order
}
// Add attribute filters as attr[id]=value
diff --git a/frontend/src/api/auth.ts b/frontend/src/api/auth.ts
index c5e1f35daf18a327bd3c1d838fc5d5a8aca8a288..837c4f4cf7068fe1ce394f49ad83131d346cff01 100644
--- a/frontend/src/api/auth.ts
+++ b/frontend/src/api/auth.ts
@@ -357,6 +357,28 @@ export async function completeLinuxDoOAuthRegistration(
return data
}
+/**
+ * Complete OIDC OAuth registration by supplying an invitation code
+ * @param pendingOAuthToken - Short-lived JWT from the OAuth callback
+ * @param invitationCode - Invitation code entered by the user
+ * @returns Token pair on success
+ */
+export async function completeOIDCOAuthRegistration(
+ pendingOAuthToken: string,
+ invitationCode: string
+): Promise<{ access_token: string; refresh_token: string; expires_in: number; token_type: string }> {
+ const { data } = await apiClient.post<{
+ access_token: string
+ refresh_token: string
+ expires_in: number
+ token_type: string
+ }>('/auth/oauth/oidc/complete-registration', {
+ pending_oauth_token: pendingOAuthToken,
+ invitation_code: invitationCode
+ })
+ return data
+}
+
export const authAPI = {
login,
login2FA,
@@ -380,7 +402,8 @@ export const authAPI = {
resetPassword,
refreshToken,
revokeAllSessions,
- completeLinuxDoOAuthRegistration
+ completeLinuxDoOAuthRegistration,
+ completeOIDCOAuthRegistration
}
export default authAPI
diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts
index 95f9ff31802a5b8a47d69f4edb8c241e80b930ba..2908c6b134833e898b17ae306564749e939d2ee9 100644
--- a/frontend/src/api/client.ts
+++ b/frontend/src/api/client.ts
@@ -92,10 +92,13 @@ apiClient.interceptors.response.use(
response.data = apiResponse.data
} else {
// API error
+ const resp = apiResponse as unknown as Record
return Promise.reject({
status: response.status,
code: apiResponse.code,
- message: apiResponse.message || 'Unknown error'
+ message: apiResponse.message || 'Unknown error',
+ reason: resp.reason,
+ metadata: resp.metadata,
})
}
}
@@ -268,7 +271,9 @@ apiClient.interceptors.response.use(
status,
code: apiData.code,
error: apiData.error,
- message: apiData.message || apiData.detail || error.message
+ message: apiData.message || apiData.detail || error.message,
+ reason: apiData.reason,
+ metadata: apiData.metadata,
})
}
diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts
index a6502a8f02be76ac63c35ba944faf696cbcb1ef5..3b38eaa59762050b73bb01b3289771fbdb0c650c 100644
--- a/frontend/src/api/index.ts
+++ b/frontend/src/api/index.ts
@@ -14,6 +14,7 @@ export { keysAPI } from './keys'
export { usageAPI } from './usage'
export { userAPI } from './user'
export { redeemAPI, type RedeemHistoryItem } from './redeem'
+export { paymentAPI } from './payment'
export { userGroupsAPI } from './groups'
export { totpAPI } from './totp'
export { default as announcementsAPI } from './announcements'
diff --git a/frontend/src/api/keys.ts b/frontend/src/api/keys.ts
index 137e10bafc99296e514f1b068cc78c5b2b16c1e9..34dd5b4b76ae0bd5db636ca4cbcb2f8152173f69 100644
--- a/frontend/src/api/keys.ts
+++ b/frontend/src/api/keys.ts
@@ -17,7 +17,13 @@ import type { ApiKey, CreateApiKeyRequest, UpdateApiKeyRequest, PaginatedRespons
export async function list(
page: number = 1,
pageSize: number = 10,
- filters?: { search?: string; status?: string; group_id?: number | string },
+ filters?: {
+ search?: string
+ status?: string
+ group_id?: number | string
+ sort_by?: string
+ sort_order?: 'asc' | 'desc'
+ },
options?: {
signal?: AbortSignal
}
diff --git a/frontend/src/api/payment.ts b/frontend/src/api/payment.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1389b60f88d386372f178a844c85d3dffad44f7a
--- /dev/null
+++ b/frontend/src/api/payment.ts
@@ -0,0 +1,79 @@
+/**
+ * User Payment API endpoints
+ * Handles payment operations for regular users
+ */
+
+import { apiClient } from './client'
+import type {
+ PaymentConfig,
+ SubscriptionPlan,
+ PaymentChannel,
+ MethodLimitsResponse,
+ CheckoutInfoResponse,
+ CreateOrderRequest,
+ CreateOrderResult,
+ PaymentOrder
+} from '@/types/payment'
+import type { BasePaginationResponse } from '@/types'
+
+export const paymentAPI = {
+ /** Get payment configuration (enabled types, limits, etc.) */
+ getConfig() {
+ return apiClient.get('/payment/config')
+ },
+
+ /** Get available subscription plans */
+ getPlans() {
+ return apiClient.get('/payment/plans')
+ },
+
+ /** Get available payment channels */
+ getChannels() {
+ return apiClient.get('/payment/channels')
+ },
+
+ /** Get all checkout page data in a single call */
+ getCheckoutInfo() {
+ return apiClient.get('/payment/checkout-info')
+ },
+
+ /** Get payment method limits and fee rates */
+ getLimits() {
+ return apiClient.get('/payment/limits')
+ },
+
+ /** Create a new payment order */
+ createOrder(data: CreateOrderRequest) {
+ return apiClient.post('/payment/orders', data)
+ },
+
+ /** Get current user's orders */
+ getMyOrders(params?: { page?: number; page_size?: number; status?: string }) {
+ return apiClient.get>('/payment/orders/my', { params })
+ },
+
+ /** Get a specific order by ID */
+ getOrder(id: number) {
+ return apiClient.get(`/payment/orders/${id}`)
+ },
+
+ /** Cancel a pending order */
+ cancelOrder(id: number) {
+ return apiClient.post(`/payment/orders/${id}/cancel`)
+ },
+
+ /** Verify order payment status with upstream provider */
+ verifyOrder(outTradeNo: string) {
+ return apiClient.post('/payment/orders/verify', { out_trade_no: outTradeNo })
+ },
+
+ /** Verify order payment status without auth (public endpoint for result page) */
+ verifyOrderPublic(outTradeNo: string) {
+ return apiClient.post('/payment/public/orders/verify', { out_trade_no: outTradeNo })
+ },
+
+ /** Request a refund for a completed order */
+ requestRefund(id: number, data: { reason: string }) {
+ return apiClient.post(`/payment/orders/${id}/refund-request`, data)
+ }
+}
diff --git a/frontend/src/api/usage.ts b/frontend/src/api/usage.ts
index 6efd7657fc4fc84ff1866cf488eb0f3b021682d2..802c428f80b9a8c43f44121161fdb90f40714461 100644
--- a/frontend/src/api/usage.ts
+++ b/frontend/src/api/usage.ts
@@ -91,7 +91,7 @@ export async function list(
* @returns Paginated list of usage logs
*/
export async function query(
- params: UsageQueryParams,
+ params: UsageQueryParams & { sort_by?: string; sort_order?: 'asc' | 'desc' },
config: { signal?: AbortSignal } = {}
): Promise> {
const { data } = await apiClient.get>('/usage', {
diff --git a/frontend/src/assets/icons/alipay.svg b/frontend/src/assets/icons/alipay.svg
new file mode 100644
index 0000000000000000000000000000000000000000..22c744c34b07bc3332cb8cc2ebccc76f148211bd
--- /dev/null
+++ b/frontend/src/assets/icons/alipay.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/frontend/src/assets/icons/easypay.svg b/frontend/src/assets/icons/easypay.svg
new file mode 100644
index 0000000000000000000000000000000000000000..7c7df9a8fa179f4f49dd7aa6e5c8c5bd2dbd1d2b
--- /dev/null
+++ b/frontend/src/assets/icons/easypay.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/frontend/src/assets/icons/stripe.svg b/frontend/src/assets/icons/stripe.svg
new file mode 100644
index 0000000000000000000000000000000000000000..7bfb7250e9752c270a8f58a6a6d59b57de30772c
--- /dev/null
+++ b/frontend/src/assets/icons/stripe.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/frontend/src/assets/icons/wxpay.svg b/frontend/src/assets/icons/wxpay.svg
new file mode 100644
index 0000000000000000000000000000000000000000..3e102acf96104218c8399a9a6a19275e2bbf3338
--- /dev/null
+++ b/frontend/src/assets/icons/wxpay.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/frontend/src/components/admin/account/AccountTableFilters.vue b/frontend/src/components/admin/account/AccountTableFilters.vue
index 6b4741830f5ff8c04cfa6a2b6b6a446be601a9a0..b33dad84e0e727b092a7f215e046b4db4d66c861 100644
--- a/frontend/src/components/admin/account/AccountTableFilters.vue
+++ b/frontend/src/components/admin/account/AccountTableFilters.vue
@@ -27,7 +27,7 @@ const updatePrivacyMode = (value: string | number | boolean | null) => { emit('u
const updateGroup = (value: string | number | boolean | null) => { emit('update:filters', { ...props.filters, group: value }) }
const pOpts = computed(() => [{ value: '', label: t('admin.accounts.allPlatforms') }, { value: 'anthropic', label: 'Anthropic' }, { value: 'openai', label: 'OpenAI' }, { value: 'gemini', label: 'Gemini' }, { value: 'antigravity', label: 'Antigravity' }])
const tOpts = computed(() => [{ value: '', label: t('admin.accounts.allTypes') }, { value: 'oauth', label: t('admin.accounts.oauthType') }, { value: 'setup-token', label: t('admin.accounts.setupToken') }, { value: 'apikey', label: t('admin.accounts.apiKey') }, { value: 'bedrock', label: 'AWS Bedrock' }])
-const sOpts = computed(() => [{ value: '', label: t('admin.accounts.allStatus') }, { value: 'active', label: t('admin.accounts.status.active') }, { value: 'inactive', label: t('admin.accounts.status.inactive') }, { value: 'error', label: t('admin.accounts.status.error') }, { value: 'rate_limited', label: t('admin.accounts.status.rateLimited') }, { value: 'temp_unschedulable', label: t('admin.accounts.status.tempUnschedulable') }])
+const sOpts = computed(() => [{ value: '', label: t('admin.accounts.allStatus') }, { value: 'active', label: t('admin.accounts.status.active') }, { value: 'inactive', label: t('admin.accounts.status.inactive') }, { value: 'error', label: t('admin.accounts.status.error') }, { value: 'rate_limited', label: t('admin.accounts.status.rateLimited') }, { value: 'temp_unschedulable', label: t('admin.accounts.status.tempUnschedulable') }, { value: 'unschedulable', label: t('admin.accounts.status.unschedulable') }])
const privacyOpts = computed(() => [
{ value: '', label: t('admin.accounts.allPrivacyModes') },
{ value: '__unset__', label: t('admin.accounts.privacyUnset') },
diff --git a/frontend/src/components/admin/account/__tests__/AccountTableFilters.spec.ts b/frontend/src/components/admin/account/__tests__/AccountTableFilters.spec.ts
deleted file mode 100644
index 5a0044e5f2a1cdc6d2805a2e69cc0029c06eafb7..0000000000000000000000000000000000000000
--- a/frontend/src/components/admin/account/__tests__/AccountTableFilters.spec.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-import { describe, expect, it, vi } from 'vitest'
-import { mount } from '@vue/test-utils'
-
-import AccountTableFilters from '../AccountTableFilters.vue'
-
-vi.mock('vue-i18n', async () => {
- const actual = await vi.importActual('vue-i18n')
- return {
- ...actual,
- useI18n: () => ({
- t: (key: string) => key
- })
- }
-})
-
-describe('AccountTableFilters', () => {
- it('renders privacy mode options and emits privacy_mode updates', async () => {
- const wrapper = mount(AccountTableFilters, {
- props: {
- searchQuery: '',
- filters: {
- platform: '',
- type: '',
- status: '',
- group: '',
- privacy_mode: ''
- },
- groups: []
- },
- global: {
- stubs: {
- SearchInput: {
- template: '
'
- },
- Select: {
- props: ['modelValue', 'options'],
- emits: ['update:modelValue', 'change'],
- template: '
'
- }
- }
- }
- })
-
- const selects = wrapper.findAll('.select-stub')
- expect(selects).toHaveLength(5)
-
- const privacyOptions = JSON.parse(selects[3].attributes('data-options'))
- expect(privacyOptions).toEqual([
- { value: '', label: 'admin.accounts.allPrivacyModes' },
- { value: '__unset__', label: 'admin.accounts.privacyUnset' },
- { value: 'training_off', label: 'Privacy' },
- { value: 'training_set_cf_blocked', label: 'CF' },
- { value: 'training_set_failed', label: 'Fail' }
- ])
- })
-})
diff --git a/frontend/src/components/admin/announcements/AnnouncementReadStatusDialog.vue b/frontend/src/components/admin/announcements/AnnouncementReadStatusDialog.vue
index a0d9de3ca8fd699bb5a403e55b3eb0842c8f4217..60c01c6dba9b917854fb6dd52ef4b5b87c37645e 100644
--- a/frontend/src/components/admin/announcements/AnnouncementReadStatusDialog.vue
+++ b/frontend/src/components/admin/announcements/AnnouncementReadStatusDialog.vue
@@ -21,7 +21,15 @@
-
+
{{ value }}
@@ -62,7 +70,7 @@
diff --git a/frontend/src/components/admin/announcements/__tests__/AnnouncementReadStatusDialog.spec.ts b/frontend/src/components/admin/announcements/__tests__/AnnouncementReadStatusDialog.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..26c87d73dcec8558371c61b44859c17ce1c7060d
--- /dev/null
+++ b/frontend/src/components/admin/announcements/__tests__/AnnouncementReadStatusDialog.spec.ts
@@ -0,0 +1,95 @@
+import { describe, it, expect, vi, beforeEach } from 'vitest'
+import { flushPromises, mount } from '@vue/test-utils'
+
+import AnnouncementReadStatusDialog from '../AnnouncementReadStatusDialog.vue'
+
+const { getReadStatus, showError } = vi.hoisted(() => ({
+ getReadStatus: vi.fn(),
+ showError: vi.fn(),
+}))
+
+vi.mock('@/api/admin', () => ({
+ adminAPI: {
+ announcements: {
+ getReadStatus,
+ },
+ },
+}))
+
+vi.mock('@/stores/app', () => ({
+ useAppStore: () => ({
+ showError,
+ }),
+}))
+
+vi.mock('vue-i18n', async () => {
+ const actual = await vi.importActual('vue-i18n')
+ return {
+ ...actual,
+ useI18n: () => ({
+ t: (key: string) => key,
+ }),
+ }
+})
+
+vi.mock('@/composables/usePersistedPageSize', () => ({
+ getPersistedPageSize: () => 20,
+}))
+
+const BaseDialogStub = {
+ props: ['show', 'title', 'width'],
+ emits: ['close'],
+ template: '
',
+}
+
+describe('AnnouncementReadStatusDialog', () => {
+ beforeEach(() => {
+ getReadStatus.mockReset()
+ showError.mockReset()
+ vi.useFakeTimers()
+ })
+
+ it('closes by aborting active requests and clearing debounced reloads', async () => {
+ let activeSignal: AbortSignal | undefined
+ getReadStatus.mockImplementation(async (...args: any[]) => {
+ activeSignal = args[4]?.signal
+ return new Promise(() => {})
+ })
+
+ const wrapper = mount(AnnouncementReadStatusDialog, {
+ props: {
+ show: false,
+ announcementId: 1,
+ },
+ global: {
+ stubs: {
+ BaseDialog: BaseDialogStub,
+ DataTable: true,
+ Pagination: true,
+ Icon: true,
+ },
+ },
+ })
+
+ await wrapper.setProps({ show: true })
+ await flushPromises()
+
+ expect(getReadStatus).toHaveBeenCalledTimes(1)
+ expect(activeSignal?.aborted).toBe(false)
+
+ const setupState = (wrapper.vm as any).$?.setupState
+ setupState.search = 'alice'
+ setupState.handleSearch()
+
+ setupState.handleClose()
+ await flushPromises()
+
+ expect(activeSignal?.aborted).toBe(true)
+ expect(wrapper.emitted('close')).toHaveLength(1)
+
+ vi.advanceTimersByTime(350)
+ await flushPromises()
+
+ expect(getReadStatus).toHaveBeenCalledTimes(1)
+ })
+})
diff --git a/frontend/src/components/admin/group/GroupRateMultipliersModal.vue b/frontend/src/components/admin/group/GroupRateMultipliersModal.vue
index cbd18af6b47d391050f30d0e10579e02a142b397..bf79bea200fae2d91cd1c385260f8eb26a211bdf 100644
--- a/frontend/src/components/admin/group/GroupRateMultipliersModal.vue
+++ b/frontend/src/components/admin/group/GroupRateMultipliersModal.vue
@@ -196,7 +196,6 @@
:total="localEntries.length"
:page="currentPage"
:page-size="pageSize"
- :page-size-options="[10, 20, 50]"
@update:page="currentPage = $event"
@update:pageSize="handlePageSizeChange"
/>
diff --git a/frontend/src/components/admin/payment/AdminOrderDetail.vue b/frontend/src/components/admin/payment/AdminOrderDetail.vue
new file mode 100644
index 0000000000000000000000000000000000000000..de4b00b5653e3b57688c6c63e9c7d2864db716ef
--- /dev/null
+++ b/frontend/src/components/admin/payment/AdminOrderDetail.vue
@@ -0,0 +1,139 @@
+
+
+
+
+
+
{{ t('payment.orders.orderId') }}
+
#{{ order.id }}
+
+
+
{{ t('payment.orders.status') }}
+
+ {{ t('payment.status.' + order.status.toLowerCase(), order.status) }}
+
+
+
+
{{ t('payment.orders.amount') }}
+
${{ order.amount.toFixed(2) }}
+
+
+
{{ t('payment.orders.payAmount') }}
+
${{ order.pay_amount.toFixed(2) }}
+
+
+
{{ t('payment.orders.paymentMethod') }}
+
+ {{ t('payment.methods.' + order.payment_type, order.payment_type) }}
+
+
+
+
{{ t('payment.admin.feeRate') }}
+
{{ (order.fee_rate * 100).toFixed(1) }}%
+
+
+
{{ t('payment.admin.orderType') }}
+
+ {{ t('payment.admin.' + order.order_type + 'Order', order.order_type) }}
+
+
+
+
{{ t('payment.orders.userId') }}
+
#{{ order.user_id }}
+
+
+
{{ t('payment.orders.createdAt') }}
+
{{ formatDateTime(order.created_at) }}
+
+
+
{{ t('payment.admin.expiresAt') }}
+
{{ formatDateTime(order.expires_at) }}
+
+
+
{{ t('payment.admin.paidAt') }}
+
{{ formatDateTime(order.paid_at) }}
+
+
+
{{ t('payment.admin.completedAt') }}
+
{{ formatDateTime(order.completed_at) }}
+
+
+
+
+
+ {{ t('payment.admin.refundInfo') }}
+
+
+
+ {{ t('payment.admin.refundAmount') }}:
+ ${{ order.refund_amount.toFixed(2) }}
+
+
+ {{ t('payment.admin.refundReason') }}:
+ {{ order.refund_reason }}
+
+
+
+
+
+
+ {{ t('payment.orders.cancel') }}
+
+
+ {{ t('payment.admin.retry') }}
+
+
+ {{ t('payment.admin.refund') }}
+
+
+
+
+
+
+
diff --git a/frontend/src/components/admin/payment/AdminOrderTable.vue b/frontend/src/components/admin/payment/AdminOrderTable.vue
new file mode 100644
index 0000000000000000000000000000000000000000..0a22930e8d25ae827e24270767959552bae020bf
--- /dev/null
+++ b/frontend/src/components/admin/payment/AdminOrderTable.vue
@@ -0,0 +1,227 @@
+
+
+
+
+
+
+ #{{ value }}
+
+
+
+ #{{ value }}
+
+
+
+
+ ${{ value.toFixed(2) }}
+
+ ({{ t('payment.orders.payAmount') }}: ${{ row.pay_amount.toFixed(2) }})
+
+
+
+
+
+
+ {{ t('payment.methods.' + value, value) }}
+
+
+
+
+
+ {{ t('payment.status.' + value.toLowerCase(), value) }}
+
+
+
+
+
+ {{ t('payment.admin.' + value + 'Order', value) }}
+
+
+
+
+ {{ formatDateTime(value) }}
+
+
+
+
+
+
+ {{ t('common.view') }}
+
+
+
+ {{ t('payment.orders.cancel') }}
+
+
+
+ {{ t('payment.admin.retry') }}
+
+
+
+ {{ t('payment.admin.refund') }}
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/components/admin/payment/AdminRefundDialog.vue b/frontend/src/components/admin/payment/AdminRefundDialog.vue
new file mode 100644
index 0000000000000000000000000000000000000000..9333828c30a8c050836bae619cde04024a71712c
--- /dev/null
+++ b/frontend/src/components/admin/payment/AdminRefundDialog.vue
@@ -0,0 +1,234 @@
+
+
+
+
+
+
+
+ {{ t('common.cancel') }}
+
+
+ {{ submitting ? t('common.processing') : t('payment.admin.confirmRefund') }}
+
+
+
+
+
+
+
diff --git a/frontend/src/components/admin/payment/DailyRevenueChart.vue b/frontend/src/components/admin/payment/DailyRevenueChart.vue
new file mode 100644
index 0000000000000000000000000000000000000000..77583da17b3aaf24a405240211bcb7abfcc6d1f4
--- /dev/null
+++ b/frontend/src/components/admin/payment/DailyRevenueChart.vue
@@ -0,0 +1,99 @@
+
+
+
+ {{ t('payment.admin.dailyRevenue') }}
+
+
+
+
+
+
+
+ {{ t('payment.admin.noData') }}
+
+
+
+
+
+
diff --git a/frontend/src/components/admin/payment/OrderStatsCards.vue b/frontend/src/components/admin/payment/OrderStatsCards.vue
new file mode 100644
index 0000000000000000000000000000000000000000..0f7ec956d9896905f1b42a20fb4a07ccd416b667
--- /dev/null
+++ b/frontend/src/components/admin/payment/OrderStatsCards.vue
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
{{ t('payment.admin.todayRevenue') }}
+
${{ formatMoney(stats.today_amount) }}
+
+ {{ stats.today_count }} {{ t('payment.admin.orders') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ t('payment.admin.totalRevenue') }}
+
${{ formatMoney(stats.total_amount) }}
+
+ {{ stats.total_count }} {{ t('payment.admin.orders') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ t('payment.admin.todayOrders') }}
+
{{ stats.today_count }}
+
+
+
+
+
+
+
+
+
+
+
+
{{ t('payment.admin.avgAmount') }}
+
${{ formatMoney(stats.avg_amount) }}
+
+
+
+
+
+
+
diff --git a/frontend/src/components/admin/payment/PaymentMethodChart.vue b/frontend/src/components/admin/payment/PaymentMethodChart.vue
new file mode 100644
index 0000000000000000000000000000000000000000..dd6909e95bc3488d83be433ba17cf5554a3554c0
--- /dev/null
+++ b/frontend/src/components/admin/payment/PaymentMethodChart.vue
@@ -0,0 +1,75 @@
+
+
+
+ {{ t('payment.admin.paymentDistribution') }}
+
+
+ {{ t('payment.admin.noData') }}
+
+
+
+
+
+
+
+ {{ t('payment.methods.' + method.type, method.type) }}
+
+
+
+
+ ${{ method.amount.toFixed(2) }}
+
+
+ ({{ method.count }})
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/components/admin/payment/TopUsersLeaderboard.vue b/frontend/src/components/admin/payment/TopUsersLeaderboard.vue
new file mode 100644
index 0000000000000000000000000000000000000000..1c85fcf1613a79d61992147fb42e25433093b78e
--- /dev/null
+++ b/frontend/src/components/admin/payment/TopUsersLeaderboard.vue
@@ -0,0 +1,52 @@
+
+
+
+ {{ t('payment.admin.topUsers') }}
+
+
+ {{ t('payment.admin.noData') }}
+
+
+
+
+
+ {{ idx + 1 }}
+
+ {{ user.email }}
+
+
+ ${{ user.amount.toFixed(2) }}
+
+
+
+
+
+
+
diff --git a/frontend/src/components/admin/usage/UsageTable.vue b/frontend/src/components/admin/usage/UsageTable.vue
index 9bbdb380b6b7b20879f1e9035027d6c13d448be6..f4494e69c246844a18484e80c3427d56af199e55 100644
--- a/frontend/src/components/admin/usage/UsageTable.vue
+++ b/frontend/src/components/admin/usage/UsageTable.vue
@@ -1,7 +1,15 @@
-
+ $emit('sort', key, order)"
+ >
(), {
+ loading: false,
+ serverSideSort: false,
+ defaultSortKey: '',
+ defaultSortOrder: 'asc'
+})
+defineEmits<{
+ userClick: [userID: number, email?: string]
+ sort: [key: string, order: 'asc' | 'desc']
+}>()
const { t } = useI18n()
// Tooltip state - cost
diff --git a/frontend/src/components/auth/LinuxDoOAuthSection.vue b/frontend/src/components/auth/LinuxDoOAuthSection.vue
index 8012b1011dae34eea7c1b2dc9c7f23b7440c6eca..c740d06fa3979b2dddf0f6db52622972d3c14b77 100644
--- a/frontend/src/components/auth/LinuxDoOAuthSection.vue
+++ b/frontend/src/components/auth/LinuxDoOAuthSection.vue
@@ -29,10 +29,10 @@
{{ t('auth.linuxdo.signIn') }}
-
+
- {{ t('auth.linuxdo.orContinue') }}
+ {{ t('auth.oauthOrContinue') }}
@@ -43,9 +43,12 @@
import { useRoute } from 'vue-router'
import { useI18n } from 'vue-i18n'
-defineProps<{
+withDefaults(defineProps<{
disabled?: boolean
-}>()
+ showDivider?: boolean
+}>(), {
+ showDivider: true
+})
const route = useRoute()
const { t } = useI18n()
@@ -58,4 +61,3 @@ function startLogin(): void {
window.location.href = startURL
}
-
diff --git a/frontend/src/components/auth/OidcOAuthSection.vue b/frontend/src/components/auth/OidcOAuthSection.vue
new file mode 100644
index 0000000000000000000000000000000000000000..f7cc7fa30fb07136f56f1e5ba08397478926efd3
--- /dev/null
+++ b/frontend/src/components/auth/OidcOAuthSection.vue
@@ -0,0 +1,53 @@
+
+
+
+
+ {{ providerInitial }}
+
+ {{ t('auth.oidc.signIn', { providerName: normalizedProviderName }) }}
+
+
+
+
+
+ {{ t('auth.oauthOrContinue') }}
+
+
+
+
+
+
+
diff --git a/frontend/src/components/common/DataTable.vue b/frontend/src/components/common/DataTable.vue
index 159fbd84d2461919ce4e3bf2a031d03b28da8a0a..36c7e2781700034ea3472bf9f860b14b7b0b5d69 100644
--- a/frontend/src/components/common/DataTable.vue
+++ b/frontend/src/components/common/DataTable.vue
@@ -68,7 +68,7 @@
'is-scrollable': isScrollable
}"
>
-