feat: add WeChat Pay, Redis logging, storage tiers and personal albums
Replace virtual payments with WeChat Pay API v3 and verify callbacks and order polling. Cache WeChat access tokens in Redis and write structured Zap logs. Support configurable capacity plans across the API, admin and mini program, with purchase snapshots and quota enforcement. Allow personal albums before binding and share them with the current partner. Update deployment configuration and domain routing.
This commit is contained in:
@@ -45,7 +45,8 @@ type User struct {
|
||||
SessionKey string `gorm:"column:session_key;type:varchar(128);default:''" json:"session_key"`
|
||||
StorageVipExpireAt *time.Time `gorm:"column:storage_vip_expire_at;type:timestamptz" json:"storage_vip_expire_at"`
|
||||
StorageQuotaMb int64 `gorm:"column:storage_quota_mb;type:bigint;default:0" json:"storage_quota_mb"`
|
||||
StoragePlan string `gorm:"column:storage_plan;type:varchar(20);default:''" json:"storage_plan"`
|
||||
StoragePlanName string `gorm:"type:varchar(80);not null;default:''" json:"storage_plan_name"`
|
||||
StoragePlan string `gorm:"column:storage_plan;type:varchar(64);default:''" json:"storage_plan"`
|
||||
StoragePlanStartedAt *time.Time `gorm:"column:storage_plan_started_at;type:timestamptz" json:"storage_plan_started_at"`
|
||||
SmokingCardVisible int64 `gorm:"column:smoking_card_visible;type:bigint;not null;default:1" json:"smoking_card_visible"`
|
||||
AvatarFileId *string `gorm:"type:uuid;index" json:"avatar_file_id"`
|
||||
@@ -116,7 +117,7 @@ type Timeline struct {
|
||||
|
||||
type Album struct {
|
||||
BaseModel
|
||||
CoupleId string `gorm:"column:couple_id;type:uuid;not null" json:"couple_id"`
|
||||
CoupleId *string `gorm:"column:couple_id;type:uuid;index" json:"couple_id"`
|
||||
Title string `gorm:"column:title;type:varchar(255);not null" json:"title"`
|
||||
Description string `gorm:"column:description;type:text" json:"description"`
|
||||
Cover string `gorm:"column:cover;type:varchar(500);default:''" json:"cover"`
|
||||
@@ -415,15 +416,46 @@ type PetDynamic struct {
|
||||
Message string `gorm:"column:message;type:text" json:"message"`
|
||||
}
|
||||
|
||||
type StorageOrder struct {
|
||||
type StoragePlan struct {
|
||||
BaseModel
|
||||
Key string `gorm:"type:varchar(64);not null;uniqueIndex" json:"key"`
|
||||
Name string `gorm:"type:varchar(80);not null" json:"name"`
|
||||
QuotaMb int64 `gorm:"not null" json:"quota_mb"` // 0 = unlimited
|
||||
Months int64 `gorm:"not null" json:"months"`
|
||||
PriceCents int64 `gorm:"not null" json:"price_cents"`
|
||||
Enabled bool `gorm:"not null" json:"enabled"`
|
||||
SortOrder int64 `gorm:"not null;default:0" json:"sort_order"`
|
||||
}
|
||||
|
||||
// Entitlements are immutable purchase snapshots; different capacities expire independently.
|
||||
type StorageEntitlement struct {
|
||||
BaseModel
|
||||
UserId string `gorm:"type:uuid;not null;index" json:"user_id"`
|
||||
OrderId *string `gorm:"type:uuid;uniqueIndex" json:"order_id"`
|
||||
SourceKey string `gorm:"type:varchar(100);uniqueIndex;not null" json:"source_key"`
|
||||
PlanKey string `gorm:"type:varchar(64);not null" json:"plan_key"`
|
||||
PlanName string `gorm:"type:varchar(80);not null" json:"plan_name"`
|
||||
QuotaMb int64 `gorm:"not null" json:"quota_mb"`
|
||||
StartsAt time.Time `gorm:"type:timestamptz;not null" json:"starts_at"`
|
||||
ExpiresAt time.Time `gorm:"type:timestamptz;not null;index" json:"expires_at"`
|
||||
}
|
||||
|
||||
type StorageOrder struct {
|
||||
PlanName string `gorm:"type:varchar(80);not null;default:''" json:"plan_name"`
|
||||
PlanQuotaMb int64 `gorm:"not null;default:0" json:"plan_quota_mb"`
|
||||
PlanMonths int64 `gorm:"not null;default:0" json:"plan_months"`
|
||||
BaseModel
|
||||
PayProvider string `gorm:"type:varchar(20);not null;default:'wechat'" json:"pay_provider"`
|
||||
PayAppId string `gorm:"type:varchar(64);not null;default:''" json:"pay_app_id"`
|
||||
PayMchId string `gorm:"type:varchar(32);not null;default:''" json:"pay_mch_id"`
|
||||
PayerOpenId string `gorm:"type:varchar(128);not null;default:''" json:"payer_open_id"`
|
||||
PrepayId string `gorm:"type:varchar(128);not null;default:''" json:"prepay_id"`
|
||||
ExpiresAt *time.Time `gorm:"type:timestamptz" json:"expires_at"`
|
||||
UserId string `gorm:"column:user_id;type:uuid;not null;index:ix_storage_orders_idx_user,priority:1" json:"user_id"`
|
||||
CoupleId *string `gorm:"column:couple_id;type:uuid" json:"couple_id"`
|
||||
OutTradeNo string `gorm:"column:out_trade_no;type:varchar(64);not null;uniqueIndex:ux_storage_orders_out_trade_no,where:deleted_at IS NULL" json:"out_trade_no"`
|
||||
PlanKey string `gorm:"column:plan_key;type:varchar(20);not null" json:"plan_key"`
|
||||
ProductId string `gorm:"column:product_id;type:varchar(64);not null;default:''" json:"product_id"`
|
||||
PlanKey string `gorm:"column:plan_key;type:varchar(64);not null" json:"plan_key"`
|
||||
AmountCents int64 `gorm:"column:amount_cents;type:bigint;not null;default:0" json:"amount_cents"`
|
||||
Env int64 `gorm:"column:env;type:bigint;not null;default:1" json:"env"`
|
||||
Status string `gorm:"column:status;type:varchar(20);not null;default:'pending';index:ix_storage_orders_idx_status,priority:1" json:"status"`
|
||||
WxOrderId string `gorm:"column:wx_order_id;type:varchar(64);not null;default:''" json:"wx_order_id"`
|
||||
Attach string `gorm:"column:attach;type:text" json:"attach"`
|
||||
@@ -465,10 +497,10 @@ type SmokingEntry struct {
|
||||
|
||||
// Models is the complete AutoMigrate registry.
|
||||
func Models() []any {
|
||||
return []any{&File{}, &FileUpload{}, &RecordFile{}, &UnbindingFileCleanup{}, &User{}, &Couple{}, &Invitation{}, &UnbindRequest{}, &InviteLink{}, &Timeline{}, &Album{}, &Photo{}, &Diary{}, &Song{}, &Checkin{}, &CheckinLog{}, &SmokingAccount{}, &SmokingLog{}, &PeriodRecord{}, &PeriodDailyRecord{}, &PeriodSexRecord{}, &Setting{}, &DiaryComment{}, &SubscribeTemplate{}, &ActivityLog{}, &Admin{}, &UserToken{}, &SubscribeMessageLog{}, &NotifyLog{}, &Pet{}, &PetDaily{}, &PetAchievement{}, &PetItem{}, &PetDynamic{}, &StorageOrder{}, &DiaryLike{}, &DiaryFavorite{}, &SmokingWallet{}, &SmokingEntry{}}
|
||||
return []any{&File{}, &FileUpload{}, &RecordFile{}, &UnbindingFileCleanup{}, &User{}, &Couple{}, &Invitation{}, &UnbindRequest{}, &InviteLink{}, &Timeline{}, &Album{}, &Photo{}, &Diary{}, &Song{}, &Checkin{}, &CheckinLog{}, &SmokingAccount{}, &SmokingLog{}, &PeriodRecord{}, &PeriodDailyRecord{}, &PeriodSexRecord{}, &Setting{}, &DiaryComment{}, &SubscribeTemplate{}, &ActivityLog{}, &Admin{}, &UserToken{}, &SubscribeMessageLog{}, &NotifyLog{}, &Pet{}, &PetDaily{}, &PetAchievement{}, &PetItem{}, &PetDynamic{}, &StorageOrder{}, &StoragePlan{}, &StorageEntitlement{}, &DiaryLike{}, &DiaryFavorite{}, &SmokingWallet{}, &SmokingEntry{}}
|
||||
}
|
||||
|
||||
// LegacyTables names logical tables used by existing report queries.
|
||||
func LegacyTables() map[string]any {
|
||||
return map[string]any{"files": &File{}, "file_uploads": &FileUpload{}, "record_files": &RecordFile{}, "unbinding_file_cleanup": &UnbindingFileCleanup{}, "users": &User{}, "couples": &Couple{}, "invitations": &Invitation{}, "unbind_requests": &UnbindRequest{}, "invite_links": &InviteLink{}, "timeline": &Timeline{}, "albums": &Album{}, "photos": &Photo{}, "diaries": &Diary{}, "songs": &Song{}, "checkins": &Checkin{}, "checkin_logs": &CheckinLog{}, "smoking_accounts": &SmokingAccount{}, "smoking_logs": &SmokingLog{}, "period_records": &PeriodRecord{}, "period_daily_records": &PeriodDailyRecord{}, "period_sex_records": &PeriodSexRecord{}, "settings": &Setting{}, "diary_comments": &DiaryComment{}, "subscribe_templates": &SubscribeTemplate{}, "activity_log": &ActivityLog{}, "admins": &Admin{}, "user_tokens": &UserToken{}, "subscribe_message_logs": &SubscribeMessageLog{}, "notify_log": &NotifyLog{}, "pets": &Pet{}, "pet_dailies": &PetDaily{}, "pet_achievements": &PetAchievement{}, "pet_items": &PetItem{}, "pet_dynamics": &PetDynamic{}, "storage_orders": &StorageOrder{}, "diary_likes": &DiaryLike{}, "diary_favorites": &DiaryFavorite{}, "smoking_wallets": &SmokingWallet{}, "smoking_entries": &SmokingEntry{}}
|
||||
return map[string]any{"files": &File{}, "file_uploads": &FileUpload{}, "record_files": &RecordFile{}, "unbinding_file_cleanup": &UnbindingFileCleanup{}, "users": &User{}, "couples": &Couple{}, "invitations": &Invitation{}, "unbind_requests": &UnbindRequest{}, "invite_links": &InviteLink{}, "timeline": &Timeline{}, "albums": &Album{}, "photos": &Photo{}, "diaries": &Diary{}, "songs": &Song{}, "checkins": &Checkin{}, "checkin_logs": &CheckinLog{}, "smoking_accounts": &SmokingAccount{}, "smoking_logs": &SmokingLog{}, "period_records": &PeriodRecord{}, "period_daily_records": &PeriodDailyRecord{}, "period_sex_records": &PeriodSexRecord{}, "settings": &Setting{}, "diary_comments": &DiaryComment{}, "subscribe_templates": &SubscribeTemplate{}, "activity_log": &ActivityLog{}, "admins": &Admin{}, "user_tokens": &UserToken{}, "subscribe_message_logs": &SubscribeMessageLog{}, "notify_log": &NotifyLog{}, "pets": &Pet{}, "pet_dailies": &PetDaily{}, "pet_achievements": &PetAchievement{}, "pet_items": &PetItem{}, "pet_dynamics": &PetDynamic{}, "storage_orders": &StorageOrder{}, "storage_plans": &StoragePlan{}, "storage_entitlements": &StorageEntitlement{}, "diary_likes": &DiaryLike{}, "diary_favorites": &DiaryFavorite{}, "smoking_wallets": &SmokingWallet{}, "smoking_entries": &SmokingEntry{}}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user