mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 17:30:23 +08:00
feat: add payment gateway and billing plans
This commit is contained in:
@@ -1671,6 +1671,21 @@ impl GatewayDataState {
|
||||
let mut groups = repository
|
||||
.list_user_groups_for_user(&snapshot.user_id)
|
||||
.await?;
|
||||
let dynamic_group_ids = self
|
||||
.active_membership_group_ids_for_user(&snapshot.user_id)
|
||||
.await?;
|
||||
if !dynamic_group_ids.is_empty() {
|
||||
groups.extend(
|
||||
repository
|
||||
.list_user_groups_by_ids(&dynamic_group_ids)
|
||||
.await?,
|
||||
);
|
||||
let mut deduped = std::collections::BTreeMap::new();
|
||||
for group in groups {
|
||||
deduped.insert(group.id.clone(), group);
|
||||
}
|
||||
groups = deduped.into_values().collect();
|
||||
}
|
||||
groups.sort_by(|left, right| {
|
||||
left.name
|
||||
.cmp(&right.name)
|
||||
@@ -1746,6 +1761,51 @@ impl GatewayDataState {
|
||||
);
|
||||
Ok(Some(snapshot))
|
||||
}
|
||||
|
||||
async fn active_membership_group_ids_for_user(
|
||||
&self,
|
||||
user_id: &str,
|
||||
) -> Result<Vec<String>, DataLayerError> {
|
||||
let Some(repository) = self.billing_reader.as_ref() else {
|
||||
return Ok(Vec::new());
|
||||
};
|
||||
let Some(entitlements) = repository.list_user_plan_entitlements(user_id).await? else {
|
||||
return Ok(Vec::new());
|
||||
};
|
||||
let now = chrono::Utc::now().timestamp().max(0) as u64;
|
||||
let mut group_ids = std::collections::BTreeSet::new();
|
||||
for entitlement in entitlements {
|
||||
if entitlement.status != "active"
|
||||
|| entitlement.starts_at_unix_secs > now
|
||||
|| entitlement.expires_at_unix_secs <= now
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let Some(items) = entitlement.entitlements_snapshot.as_array() else {
|
||||
continue;
|
||||
};
|
||||
for item in items {
|
||||
if item.get("type").and_then(serde_json::Value::as_str) != Some("membership_group")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let Some(groups) = item
|
||||
.get("grant_user_groups")
|
||||
.and_then(serde_json::Value::as_array)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
for group_id in groups {
|
||||
if let Some(group_id) = group_id.as_str().map(str::trim) {
|
||||
if !group_id.is_empty() {
|
||||
group_ids.insert(group_id.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(group_ids.into_iter().collect())
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_admin_unrestricted_auth_snapshot(snapshot: &mut GatewayAuthApiKeySnapshot) {
|
||||
|
||||
@@ -62,9 +62,9 @@ use aether_data::repository::wallet::{
|
||||
AdminRedeemCodeListQuery, AdminWalletLedgerQuery, AdminWalletListQuery,
|
||||
AdminWalletRefundRequestListQuery, CompleteAdminWalletRefundInput,
|
||||
CreateAdminRedeemCodeBatchInput, CreateAdminRedeemCodeBatchResult,
|
||||
CreateManualWalletRechargeInput, CreateWalletRechargeOrderInput,
|
||||
CreateWalletRechargeOrderOutcome, CreateWalletRefundRequestInput,
|
||||
CreateWalletRefundRequestOutcome, CreditAdminPaymentOrderInput,
|
||||
CreateManualWalletRechargeInput, CreatePlanPurchaseOrderInput, CreatePlanPurchaseOrderOutcome,
|
||||
CreateWalletRechargeOrderInput, CreateWalletRechargeOrderOutcome,
|
||||
CreateWalletRefundRequestInput, CreateWalletRefundRequestOutcome, CreditAdminPaymentOrderInput,
|
||||
DeleteAdminRedeemCodeBatchInput, DisableAdminRedeemCodeBatchInput, DisableAdminRedeemCodeInput,
|
||||
FailAdminWalletRefundInput, ProcessAdminWalletRefundInput, ProcessPaymentCallbackInput,
|
||||
ProcessPaymentCallbackOutcome, RedeemWalletCodeInput, RedeemWalletCodeOutcome,
|
||||
@@ -89,7 +89,9 @@ use aether_data_contracts::repository::background_tasks::{
|
||||
use aether_data_contracts::repository::billing::{
|
||||
AdminBillingCollectorRecord, AdminBillingCollectorWriteInput, AdminBillingMutationOutcome,
|
||||
AdminBillingPresetApplyResult, AdminBillingRuleRecord, AdminBillingRuleWriteInput,
|
||||
BillingReadRepository, StoredBillingModelContext,
|
||||
BillingPlanRecord, BillingPlanWriteInput, BillingReadRepository, PaymentGatewayConfigRecord,
|
||||
PaymentGatewayConfigWriteInput, StoredBillingModelContext, UserDailyQuotaAvailabilityRecord,
|
||||
UserPlanEntitlementRecord,
|
||||
};
|
||||
use aether_data_contracts::repository::candidate_selection::{
|
||||
MinimalCandidateSelectionReadRepository, StoredMinimalCandidateSelectionRow,
|
||||
|
||||
@@ -5,31 +5,34 @@ use super::{
|
||||
AdminBillingRuleWriteInput, AdminPaymentOrderListQuery, AdminRedeemCodeBatchListQuery,
|
||||
AdminRedeemCodeListQuery, AdminWalletLedgerQuery, AdminWalletListQuery,
|
||||
AdminWalletRefundRequestListQuery, AnnouncementListQuery, AuditLogListQuery,
|
||||
BackgroundTaskListQuery, BackgroundTaskSummary, CompleteAdminWalletRefundInput,
|
||||
CreateAdminRedeemCodeBatchInput, CreateAdminRedeemCodeBatchResult, CreateAnnouncementRecord,
|
||||
CreateManualWalletRechargeInput, CreateWalletRechargeOrderInput,
|
||||
BackgroundTaskListQuery, BackgroundTaskSummary, BillingPlanRecord, BillingPlanWriteInput,
|
||||
CompleteAdminWalletRefundInput, CreateAdminRedeemCodeBatchInput,
|
||||
CreateAdminRedeemCodeBatchResult, CreateAnnouncementRecord, CreateManualWalletRechargeInput,
|
||||
CreatePlanPurchaseOrderInput, CreatePlanPurchaseOrderOutcome, CreateWalletRechargeOrderInput,
|
||||
CreateWalletRechargeOrderOutcome, CreateWalletRefundRequestInput,
|
||||
CreateWalletRefundRequestOutcome, CreditAdminPaymentOrderInput, DataLayerError,
|
||||
DatabaseMaintenanceSummary, DecisionTrace, DeleteAdminRedeemCodeBatchInput,
|
||||
DisableAdminRedeemCodeBatchInput, DisableAdminRedeemCodeInput, FailAdminWalletRefundInput,
|
||||
GatewayDataState, GatewayProviderTransportSnapshot, LocalVideoTaskReadResponse,
|
||||
ProcessAdminWalletRefundInput, ProcessPaymentCallbackInput, ProcessPaymentCallbackOutcome,
|
||||
RedeemWalletCodeInput, RedeemWalletCodeOutcome, RequestAuditBundle, RequestCandidateTrace,
|
||||
StoredAdminAuditLogPage, StoredAdminPaymentCallbackPage, StoredAdminPaymentOrder,
|
||||
StoredAdminPaymentOrderPage, StoredAdminRedeemCodeBatch, StoredAdminRedeemCodeBatchPage,
|
||||
StoredAdminRedeemCodePage, StoredAdminWalletLedgerPage, StoredAdminWalletListPage,
|
||||
StoredAdminWalletRefund, StoredAdminWalletRefundPage, StoredAdminWalletRefundRequestPage,
|
||||
StoredAdminWalletTransaction, StoredAdminWalletTransactionPage, StoredAnnouncement,
|
||||
StoredAnnouncementPage, StoredBackgroundTaskEvent, StoredBackgroundTaskRun,
|
||||
StoredBackgroundTaskRunPage, StoredBillingModelContext, StoredProviderQuotaSnapshot,
|
||||
StoredProviderUsageSummary, StoredRequestUsageAudit, StoredSuspiciousActivity,
|
||||
StoredUsageSettlement, StoredUserAuditLogPage, StoredUserAuthRecord, StoredUserExportRow,
|
||||
StoredUserSummary, StoredVideoTask, StoredWalletDailyUsageLedger,
|
||||
StoredWalletDailyUsageLedgerPage, StoredWalletSnapshot, UpdateAnnouncementRecord,
|
||||
UpsertBackgroundTaskEvent, UpsertBackgroundTaskRun, UpsertUsageRecord, UpsertVideoTask,
|
||||
UsageSettlementInput, VideoTaskLookupKey, VideoTaskModelCount, VideoTaskQueryFilter,
|
||||
VideoTaskStatusCount, WalletDailyUsageAggregationInput, WalletDailyUsageAggregationResult,
|
||||
WalletLookupKey, WalletMutationOutcome,
|
||||
PaymentGatewayConfigRecord, PaymentGatewayConfigWriteInput, ProcessAdminWalletRefundInput,
|
||||
ProcessPaymentCallbackInput, ProcessPaymentCallbackOutcome, RedeemWalletCodeInput,
|
||||
RedeemWalletCodeOutcome, RequestAuditBundle, RequestCandidateTrace, StoredAdminAuditLogPage,
|
||||
StoredAdminPaymentCallbackPage, StoredAdminPaymentOrder, StoredAdminPaymentOrderPage,
|
||||
StoredAdminRedeemCodeBatch, StoredAdminRedeemCodeBatchPage, StoredAdminRedeemCodePage,
|
||||
StoredAdminWalletLedgerPage, StoredAdminWalletListPage, StoredAdminWalletRefund,
|
||||
StoredAdminWalletRefundPage, StoredAdminWalletRefundRequestPage, StoredAdminWalletTransaction,
|
||||
StoredAdminWalletTransactionPage, StoredAnnouncement, StoredAnnouncementPage,
|
||||
StoredBackgroundTaskEvent, StoredBackgroundTaskRun, StoredBackgroundTaskRunPage,
|
||||
StoredBillingModelContext, StoredProviderQuotaSnapshot, StoredProviderUsageSummary,
|
||||
StoredRequestUsageAudit, StoredSuspiciousActivity, StoredUsageSettlement,
|
||||
StoredUserAuditLogPage, StoredUserAuthRecord, StoredUserExportRow, StoredUserSummary,
|
||||
StoredVideoTask, StoredWalletDailyUsageLedger, StoredWalletDailyUsageLedgerPage,
|
||||
StoredWalletSnapshot, UpdateAnnouncementRecord, UpsertBackgroundTaskEvent,
|
||||
UpsertBackgroundTaskRun, UpsertUsageRecord, UpsertVideoTask, UsageSettlementInput,
|
||||
UserDailyQuotaAvailabilityRecord, UserPlanEntitlementRecord, VideoTaskLookupKey,
|
||||
VideoTaskModelCount, VideoTaskQueryFilter, VideoTaskStatusCount,
|
||||
WalletDailyUsageAggregationInput, WalletDailyUsageAggregationResult, WalletLookupKey,
|
||||
WalletMutationOutcome,
|
||||
};
|
||||
use aether_data_contracts::repository::usage::{
|
||||
PendingUsageCleanupSummary, ProviderApiKeyWindowUsageRequest,
|
||||
@@ -675,6 +678,16 @@ impl GatewayDataState {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn create_plan_purchase_order(
|
||||
&self,
|
||||
input: CreatePlanPurchaseOrderInput,
|
||||
) -> Result<Option<CreatePlanPurchaseOrderOutcome>, DataLayerError> {
|
||||
match &self.wallet_writer {
|
||||
Some(repository) => repository.create_plan_purchase_order(input).await.map(Some),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn create_wallet_refund_request(
|
||||
&self,
|
||||
input: CreateWalletRefundRequestInput,
|
||||
@@ -1663,6 +1676,108 @@ impl GatewayDataState {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn find_payment_gateway_config(
|
||||
&self,
|
||||
provider: &str,
|
||||
) -> Result<Option<PaymentGatewayConfigRecord>, DataLayerError> {
|
||||
match &self.billing_reader {
|
||||
Some(repository) => repository.find_payment_gateway_config(provider).await,
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn upsert_payment_gateway_config(
|
||||
&self,
|
||||
input: &PaymentGatewayConfigWriteInput,
|
||||
) -> Result<AdminBillingMutationOutcome<PaymentGatewayConfigRecord>, DataLayerError> {
|
||||
match &self.billing_reader {
|
||||
Some(repository) => repository.upsert_payment_gateway_config(input).await,
|
||||
None => Ok(AdminBillingMutationOutcome::Unavailable),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn list_billing_plans(
|
||||
&self,
|
||||
include_disabled: bool,
|
||||
) -> Result<Option<Vec<BillingPlanRecord>>, DataLayerError> {
|
||||
match &self.billing_reader {
|
||||
Some(repository) => repository.list_billing_plans(include_disabled).await,
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn find_billing_plan(
|
||||
&self,
|
||||
plan_id: &str,
|
||||
) -> Result<Option<BillingPlanRecord>, DataLayerError> {
|
||||
match &self.billing_reader {
|
||||
Some(repository) => repository.find_billing_plan(plan_id).await,
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn create_billing_plan(
|
||||
&self,
|
||||
input: &BillingPlanWriteInput,
|
||||
) -> Result<AdminBillingMutationOutcome<BillingPlanRecord>, DataLayerError> {
|
||||
match &self.billing_reader {
|
||||
Some(repository) => repository.create_billing_plan(input).await,
|
||||
None => Ok(AdminBillingMutationOutcome::Unavailable),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn update_billing_plan(
|
||||
&self,
|
||||
plan_id: &str,
|
||||
input: &BillingPlanWriteInput,
|
||||
) -> Result<AdminBillingMutationOutcome<BillingPlanRecord>, DataLayerError> {
|
||||
match &self.billing_reader {
|
||||
Some(repository) => repository.update_billing_plan(plan_id, input).await,
|
||||
None => Ok(AdminBillingMutationOutcome::Unavailable),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn set_billing_plan_enabled(
|
||||
&self,
|
||||
plan_id: &str,
|
||||
enabled: bool,
|
||||
) -> Result<AdminBillingMutationOutcome<BillingPlanRecord>, DataLayerError> {
|
||||
match &self.billing_reader {
|
||||
Some(repository) => repository.set_billing_plan_enabled(plan_id, enabled).await,
|
||||
None => Ok(AdminBillingMutationOutcome::Unavailable),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn delete_billing_plan(
|
||||
&self,
|
||||
plan_id: &str,
|
||||
) -> Result<AdminBillingMutationOutcome<()>, DataLayerError> {
|
||||
match &self.billing_reader {
|
||||
Some(repository) => repository.delete_billing_plan(plan_id).await,
|
||||
None => Ok(AdminBillingMutationOutcome::Unavailable),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn list_user_plan_entitlements(
|
||||
&self,
|
||||
user_id: &str,
|
||||
) -> Result<Option<Vec<UserPlanEntitlementRecord>>, DataLayerError> {
|
||||
match &self.billing_reader {
|
||||
Some(repository) => repository.list_user_plan_entitlements(user_id).await,
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn find_user_daily_quota_availability(
|
||||
&self,
|
||||
user_id: &str,
|
||||
) -> Result<Option<UserDailyQuotaAvailabilityRecord>, DataLayerError> {
|
||||
match &self.billing_reader {
|
||||
Some(repository) => repository.find_user_daily_quota_availability(user_id).await,
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn read_request_candidate_trace(
|
||||
&self,
|
||||
request_id: &str,
|
||||
|
||||
Reference in New Issue
Block a user