diff --git a/backend/ent/migrate/schema.go b/backend/ent/migrate/schema.go index 230ea060..81f6a664 100644 --- a/backend/ent/migrate/schema.go +++ b/backend/ent/migrate/schema.go @@ -655,6 +655,7 @@ var ( {Name: "subscription_days", Type: field.TypeInt, Nullable: true}, {Name: "provider_instance_id", Type: field.TypeString, Nullable: true, Size: 64}, {Name: "provider_key", Type: field.TypeString, Nullable: true, Size: 30}, + {Name: "provider_snapshot", Type: field.TypeJSON, Nullable: true, SchemaType: map[string]string{"postgres": "jsonb"}}, {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"}}, @@ -683,7 +684,7 @@ var ( ForeignKeys: []*schema.ForeignKey{ { Symbol: "payment_orders_users_payment_orders", - Columns: []*schema.Column{PaymentOrdersColumns[38]}, + Columns: []*schema.Column{PaymentOrdersColumns[39]}, RefColumns: []*schema.Column{UsersColumns[0]}, OnDelete: schema.NoAction, }, @@ -697,32 +698,32 @@ var ( { Name: "paymentorder_user_id", Unique: false, - Columns: []*schema.Column{PaymentOrdersColumns[38]}, + Columns: []*schema.Column{PaymentOrdersColumns[39]}, }, { Name: "paymentorder_status", Unique: false, - Columns: []*schema.Column{PaymentOrdersColumns[20]}, + Columns: []*schema.Column{PaymentOrdersColumns[21]}, }, { Name: "paymentorder_expires_at", Unique: false, - Columns: []*schema.Column{PaymentOrdersColumns[28]}, + Columns: []*schema.Column{PaymentOrdersColumns[29]}, }, { Name: "paymentorder_created_at", Unique: false, - Columns: []*schema.Column{PaymentOrdersColumns[36]}, + Columns: []*schema.Column{PaymentOrdersColumns[37]}, }, { Name: "paymentorder_paid_at", Unique: false, - Columns: []*schema.Column{PaymentOrdersColumns[29]}, + Columns: []*schema.Column{PaymentOrdersColumns[30]}, }, { Name: "paymentorder_payment_type_paid_at", Unique: false, - Columns: []*schema.Column{PaymentOrdersColumns[9], PaymentOrdersColumns[29]}, + Columns: []*schema.Column{PaymentOrdersColumns[9], PaymentOrdersColumns[30]}, }, { Name: "paymentorder_order_type", diff --git a/backend/ent/mutation.go b/backend/ent/mutation.go index 5227015c..ec4a4070 100644 --- a/backend/ent/mutation.go +++ b/backend/ent/mutation.go @@ -15386,6 +15386,7 @@ type PaymentOrderMutation struct { addsubscription_days *int provider_instance_id *string provider_key *string + provider_snapshot *map[string]interface{} status *string refund_amount *float64 addrefund_amount *float64 @@ -16471,6 +16472,55 @@ func (m *PaymentOrderMutation) ResetProviderKey() { delete(m.clearedFields, paymentorder.FieldProviderKey) } +// SetProviderSnapshot sets the "provider_snapshot" field. +func (m *PaymentOrderMutation) SetProviderSnapshot(value map[string]interface{}) { + m.provider_snapshot = &value +} + +// ProviderSnapshot returns the value of the "provider_snapshot" field in the mutation. +func (m *PaymentOrderMutation) ProviderSnapshot() (r map[string]interface{}, exists bool) { + v := m.provider_snapshot + if v == nil { + return + } + return *v, true +} + +// OldProviderSnapshot returns the old "provider_snapshot" 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) OldProviderSnapshot(ctx context.Context) (v map[string]interface{}, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldProviderSnapshot is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldProviderSnapshot requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldProviderSnapshot: %w", err) + } + return oldValue.ProviderSnapshot, nil +} + +// ClearProviderSnapshot clears the value of the "provider_snapshot" field. +func (m *PaymentOrderMutation) ClearProviderSnapshot() { + m.provider_snapshot = nil + m.clearedFields[paymentorder.FieldProviderSnapshot] = struct{}{} +} + +// ProviderSnapshotCleared returns if the "provider_snapshot" field was cleared in this mutation. +func (m *PaymentOrderMutation) ProviderSnapshotCleared() bool { + _, ok := m.clearedFields[paymentorder.FieldProviderSnapshot] + return ok +} + +// ResetProviderSnapshot resets all changes to the "provider_snapshot" field. +func (m *PaymentOrderMutation) ResetProviderSnapshot() { + m.provider_snapshot = nil + delete(m.clearedFields, paymentorder.FieldProviderSnapshot) +} + // SetStatus sets the "status" field. func (m *PaymentOrderMutation) SetStatus(s string) { m.status = &s @@ -17330,7 +17380,7 @@ func (m *PaymentOrderMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *PaymentOrderMutation) Fields() []string { - fields := make([]string, 0, 38) + fields := make([]string, 0, 39) if m.user != nil { fields = append(fields, paymentorder.FieldUserID) } @@ -17391,6 +17441,9 @@ func (m *PaymentOrderMutation) Fields() []string { if m.provider_key != nil { fields = append(fields, paymentorder.FieldProviderKey) } + if m.provider_snapshot != nil { + fields = append(fields, paymentorder.FieldProviderSnapshot) + } if m.status != nil { fields = append(fields, paymentorder.FieldStatus) } @@ -17493,6 +17546,8 @@ func (m *PaymentOrderMutation) Field(name string) (ent.Value, bool) { return m.ProviderInstanceID() case paymentorder.FieldProviderKey: return m.ProviderKey() + case paymentorder.FieldProviderSnapshot: + return m.ProviderSnapshot() case paymentorder.FieldStatus: return m.Status() case paymentorder.FieldRefundAmount: @@ -17578,6 +17633,8 @@ func (m *PaymentOrderMutation) OldField(ctx context.Context, name string) (ent.V return m.OldProviderInstanceID(ctx) case paymentorder.FieldProviderKey: return m.OldProviderKey(ctx) + case paymentorder.FieldProviderSnapshot: + return m.OldProviderSnapshot(ctx) case paymentorder.FieldStatus: return m.OldStatus(ctx) case paymentorder.FieldRefundAmount: @@ -17763,6 +17820,13 @@ func (m *PaymentOrderMutation) SetField(name string, value ent.Value) error { } m.SetProviderKey(v) return nil + case paymentorder.FieldProviderSnapshot: + v, ok := value.(map[string]interface{}) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetProviderSnapshot(v) + return nil case paymentorder.FieldStatus: v, ok := value.(string) if !ok { @@ -18033,6 +18097,9 @@ func (m *PaymentOrderMutation) ClearedFields() []string { if m.FieldCleared(paymentorder.FieldProviderKey) { fields = append(fields, paymentorder.FieldProviderKey) } + if m.FieldCleared(paymentorder.FieldProviderSnapshot) { + fields = append(fields, paymentorder.FieldProviderSnapshot) + } if m.FieldCleared(paymentorder.FieldRefundReason) { fields = append(fields, paymentorder.FieldRefundReason) } @@ -18104,6 +18171,9 @@ func (m *PaymentOrderMutation) ClearField(name string) error { case paymentorder.FieldProviderKey: m.ClearProviderKey() return nil + case paymentorder.FieldProviderSnapshot: + m.ClearProviderSnapshot() + return nil case paymentorder.FieldRefundReason: m.ClearRefundReason() return nil @@ -18202,6 +18272,9 @@ func (m *PaymentOrderMutation) ResetField(name string) error { case paymentorder.FieldProviderKey: m.ResetProviderKey() return nil + case paymentorder.FieldProviderSnapshot: + m.ResetProviderSnapshot() + return nil case paymentorder.FieldStatus: m.ResetStatus() return nil diff --git a/backend/ent/paymentorder.go b/backend/ent/paymentorder.go index a58823ee..b131b8c8 100644 --- a/backend/ent/paymentorder.go +++ b/backend/ent/paymentorder.go @@ -3,6 +3,7 @@ package ent import ( + "encoding/json" "fmt" "strings" "time" @@ -58,6 +59,8 @@ type PaymentOrder struct { ProviderInstanceID *string `json:"provider_instance_id,omitempty"` // ProviderKey holds the value of the "provider_key" field. ProviderKey *string `json:"provider_key,omitempty"` + // ProviderSnapshot holds the value of the "provider_snapshot" field. + ProviderSnapshot map[string]interface{} `json:"provider_snapshot,omitempty"` // Status holds the value of the "status" field. Status string `json:"status,omitempty"` // RefundAmount holds the value of the "refund_amount" field. @@ -125,6 +128,8 @@ func (*PaymentOrder) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) for i := range columns { switch columns[i] { + case paymentorder.FieldProviderSnapshot: + values[i] = new([]byte) case paymentorder.FieldForceRefund: values[i] = new(sql.NullBool) case paymentorder.FieldAmount, paymentorder.FieldPayAmount, paymentorder.FieldFeeRate, paymentorder.FieldRefundAmount: @@ -285,6 +290,14 @@ func (_m *PaymentOrder) assignValues(columns []string, values []any) error { _m.ProviderKey = new(string) *_m.ProviderKey = value.String } + case paymentorder.FieldProviderSnapshot: + if value, ok := values[i].(*[]byte); !ok { + return fmt.Errorf("unexpected type %T for field provider_snapshot", values[i]) + } else if value != nil && len(*value) > 0 { + if err := json.Unmarshal(*value, &_m.ProviderSnapshot); err != nil { + return fmt.Errorf("unmarshal field provider_snapshot: %w", err) + } + } case paymentorder.FieldStatus: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field status", values[i]) @@ -522,6 +535,9 @@ func (_m *PaymentOrder) String() string { builder.WriteString(*v) } builder.WriteString(", ") + builder.WriteString("provider_snapshot=") + builder.WriteString(fmt.Sprintf("%v", _m.ProviderSnapshot)) + builder.WriteString(", ") builder.WriteString("status=") builder.WriteString(_m.Status) builder.WriteString(", ") diff --git a/backend/ent/paymentorder/paymentorder.go b/backend/ent/paymentorder/paymentorder.go index af9b1422..62883794 100644 --- a/backend/ent/paymentorder/paymentorder.go +++ b/backend/ent/paymentorder/paymentorder.go @@ -54,6 +54,8 @@ const ( FieldProviderInstanceID = "provider_instance_id" // FieldProviderKey holds the string denoting the provider_key field in the database. FieldProviderKey = "provider_key" + // FieldProviderSnapshot holds the string denoting the provider_snapshot field in the database. + FieldProviderSnapshot = "provider_snapshot" // 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. @@ -126,6 +128,7 @@ var Columns = []string{ FieldSubscriptionDays, FieldProviderInstanceID, FieldProviderKey, + FieldProviderSnapshot, FieldStatus, FieldRefundAmount, FieldRefundReason, diff --git a/backend/ent/paymentorder/where.go b/backend/ent/paymentorder/where.go index 0f6b74a0..e96bf51e 100644 --- a/backend/ent/paymentorder/where.go +++ b/backend/ent/paymentorder/where.go @@ -1440,6 +1440,16 @@ func ProviderKeyContainsFold(v string) predicate.PaymentOrder { return predicate.PaymentOrder(sql.FieldContainsFold(FieldProviderKey, v)) } +// ProviderSnapshotIsNil applies the IsNil predicate on the "provider_snapshot" field. +func ProviderSnapshotIsNil() predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIsNull(FieldProviderSnapshot)) +} + +// ProviderSnapshotNotNil applies the NotNil predicate on the "provider_snapshot" field. +func ProviderSnapshotNotNil() predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotNull(FieldProviderSnapshot)) +} + // StatusEQ applies the EQ predicate on the "status" field. func StatusEQ(v string) predicate.PaymentOrder { return predicate.PaymentOrder(sql.FieldEQ(FieldStatus, v)) diff --git a/backend/ent/paymentorder_create.go b/backend/ent/paymentorder_create.go index 497ba52c..3ee24f8e 100644 --- a/backend/ent/paymentorder_create.go +++ b/backend/ent/paymentorder_create.go @@ -239,6 +239,12 @@ func (_c *PaymentOrderCreate) SetNillableProviderKey(v *string) *PaymentOrderCre return _c } +// SetProviderSnapshot sets the "provider_snapshot" field. +func (_c *PaymentOrderCreate) SetProviderSnapshot(v map[string]interface{}) *PaymentOrderCreate { + _c.mutation.SetProviderSnapshot(v) + return _c +} + // SetStatus sets the "status" field. func (_c *PaymentOrderCreate) SetStatus(v string) *PaymentOrderCreate { _c.mutation.SetStatus(v) @@ -771,6 +777,10 @@ func (_c *PaymentOrderCreate) createSpec() (*PaymentOrder, *sqlgraph.CreateSpec) _spec.SetField(paymentorder.FieldProviderKey, field.TypeString, value) _node.ProviderKey = &value } + if value, ok := _c.mutation.ProviderSnapshot(); ok { + _spec.SetField(paymentorder.FieldProviderSnapshot, field.TypeJSON, value) + _node.ProviderSnapshot = value + } if value, ok := _c.mutation.Status(); ok { _spec.SetField(paymentorder.FieldStatus, field.TypeString, value) _node.Status = value @@ -1242,6 +1252,24 @@ func (u *PaymentOrderUpsert) ClearProviderKey() *PaymentOrderUpsert { return u } +// SetProviderSnapshot sets the "provider_snapshot" field. +func (u *PaymentOrderUpsert) SetProviderSnapshot(v map[string]interface{}) *PaymentOrderUpsert { + u.Set(paymentorder.FieldProviderSnapshot, v) + return u +} + +// UpdateProviderSnapshot sets the "provider_snapshot" field to the value that was provided on create. +func (u *PaymentOrderUpsert) UpdateProviderSnapshot() *PaymentOrderUpsert { + u.SetExcluded(paymentorder.FieldProviderSnapshot) + return u +} + +// ClearProviderSnapshot clears the value of the "provider_snapshot" field. +func (u *PaymentOrderUpsert) ClearProviderSnapshot() *PaymentOrderUpsert { + u.SetNull(paymentorder.FieldProviderSnapshot) + return u +} + // SetStatus sets the "status" field. func (u *PaymentOrderUpsert) SetStatus(v string) *PaymentOrderUpsert { u.Set(paymentorder.FieldStatus, v) @@ -1942,6 +1970,27 @@ func (u *PaymentOrderUpsertOne) ClearProviderKey() *PaymentOrderUpsertOne { }) } +// SetProviderSnapshot sets the "provider_snapshot" field. +func (u *PaymentOrderUpsertOne) SetProviderSnapshot(v map[string]interface{}) *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetProviderSnapshot(v) + }) +} + +// UpdateProviderSnapshot sets the "provider_snapshot" field to the value that was provided on create. +func (u *PaymentOrderUpsertOne) UpdateProviderSnapshot() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateProviderSnapshot() + }) +} + +// ClearProviderSnapshot clears the value of the "provider_snapshot" field. +func (u *PaymentOrderUpsertOne) ClearProviderSnapshot() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.ClearProviderSnapshot() + }) +} + // SetStatus sets the "status" field. func (u *PaymentOrderUpsertOne) SetStatus(v string) *PaymentOrderUpsertOne { return u.Update(func(s *PaymentOrderUpsert) { @@ -2853,6 +2902,27 @@ func (u *PaymentOrderUpsertBulk) ClearProviderKey() *PaymentOrderUpsertBulk { }) } +// SetProviderSnapshot sets the "provider_snapshot" field. +func (u *PaymentOrderUpsertBulk) SetProviderSnapshot(v map[string]interface{}) *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetProviderSnapshot(v) + }) +} + +// UpdateProviderSnapshot sets the "provider_snapshot" field to the value that was provided on create. +func (u *PaymentOrderUpsertBulk) UpdateProviderSnapshot() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateProviderSnapshot() + }) +} + +// ClearProviderSnapshot clears the value of the "provider_snapshot" field. +func (u *PaymentOrderUpsertBulk) ClearProviderSnapshot() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.ClearProviderSnapshot() + }) +} + // SetStatus sets the "status" field. func (u *PaymentOrderUpsertBulk) SetStatus(v string) *PaymentOrderUpsertBulk { return u.Update(func(s *PaymentOrderUpsert) { diff --git a/backend/ent/paymentorder_update.go b/backend/ent/paymentorder_update.go index 9a901415..378e0dad 100644 --- a/backend/ent/paymentorder_update.go +++ b/backend/ent/paymentorder_update.go @@ -405,6 +405,18 @@ func (_u *PaymentOrderUpdate) ClearProviderKey() *PaymentOrderUpdate { return _u } +// SetProviderSnapshot sets the "provider_snapshot" field. +func (_u *PaymentOrderUpdate) SetProviderSnapshot(v map[string]interface{}) *PaymentOrderUpdate { + _u.mutation.SetProviderSnapshot(v) + return _u +} + +// ClearProviderSnapshot clears the value of the "provider_snapshot" field. +func (_u *PaymentOrderUpdate) ClearProviderSnapshot() *PaymentOrderUpdate { + _u.mutation.ClearProviderSnapshot() + return _u +} + // SetStatus sets the "status" field. func (_u *PaymentOrderUpdate) SetStatus(v string) *PaymentOrderUpdate { _u.mutation.SetStatus(v) @@ -941,6 +953,12 @@ func (_u *PaymentOrderUpdate) sqlSave(ctx context.Context) (_node int, err error if _u.mutation.ProviderKeyCleared() { _spec.ClearField(paymentorder.FieldProviderKey, field.TypeString) } + if value, ok := _u.mutation.ProviderSnapshot(); ok { + _spec.SetField(paymentorder.FieldProviderSnapshot, field.TypeJSON, value) + } + if _u.mutation.ProviderSnapshotCleared() { + _spec.ClearField(paymentorder.FieldProviderSnapshot, field.TypeJSON) + } if value, ok := _u.mutation.Status(); ok { _spec.SetField(paymentorder.FieldStatus, field.TypeString, value) } @@ -1450,6 +1468,18 @@ func (_u *PaymentOrderUpdateOne) ClearProviderKey() *PaymentOrderUpdateOne { return _u } +// SetProviderSnapshot sets the "provider_snapshot" field. +func (_u *PaymentOrderUpdateOne) SetProviderSnapshot(v map[string]interface{}) *PaymentOrderUpdateOne { + _u.mutation.SetProviderSnapshot(v) + return _u +} + +// ClearProviderSnapshot clears the value of the "provider_snapshot" field. +func (_u *PaymentOrderUpdateOne) ClearProviderSnapshot() *PaymentOrderUpdateOne { + _u.mutation.ClearProviderSnapshot() + return _u +} + // SetStatus sets the "status" field. func (_u *PaymentOrderUpdateOne) SetStatus(v string) *PaymentOrderUpdateOne { _u.mutation.SetStatus(v) @@ -2016,6 +2046,12 @@ func (_u *PaymentOrderUpdateOne) sqlSave(ctx context.Context) (_node *PaymentOrd if _u.mutation.ProviderKeyCleared() { _spec.ClearField(paymentorder.FieldProviderKey, field.TypeString) } + if value, ok := _u.mutation.ProviderSnapshot(); ok { + _spec.SetField(paymentorder.FieldProviderSnapshot, field.TypeJSON, value) + } + if _u.mutation.ProviderSnapshotCleared() { + _spec.ClearField(paymentorder.FieldProviderSnapshot, field.TypeJSON) + } if value, ok := _u.mutation.Status(); ok { _spec.SetField(paymentorder.FieldStatus, field.TypeString, value) } diff --git a/backend/ent/runtime/runtime.go b/backend/ent/runtime/runtime.go index b7118ac9..bdb7f7a9 100644 --- a/backend/ent/runtime/runtime.go +++ b/backend/ent/runtime/runtime.go @@ -728,37 +728,37 @@ func init() { // paymentorder.ProviderKeyValidator is a validator for the "provider_key" field. It is called by the builders before save. paymentorder.ProviderKeyValidator = paymentorderDescProviderKey.Validators[0].(func(string) error) // paymentorderDescStatus is the schema descriptor for status field. - paymentorderDescStatus := paymentorderFields[20].Descriptor() + paymentorderDescStatus := paymentorderFields[21].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[21].Descriptor() + paymentorderDescRefundAmount := paymentorderFields[22].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[24].Descriptor() + paymentorderDescForceRefund := paymentorderFields[25].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[27].Descriptor() + paymentorderDescRefundRequestedBy := paymentorderFields[28].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[33].Descriptor() + paymentorderDescClientIP := paymentorderFields[34].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[34].Descriptor() + paymentorderDescSrcHost := paymentorderFields[35].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[36].Descriptor() + paymentorderDescCreatedAt := paymentorderFields[37].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[37].Descriptor() + paymentorderDescUpdatedAt := paymentorderFields[38].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. diff --git a/backend/ent/schema/payment_order.go b/backend/ent/schema/payment_order.go index 64378de1..5815d032 100644 --- a/backend/ent/schema/payment_order.go +++ b/backend/ent/schema/payment_order.go @@ -95,6 +95,9 @@ func (PaymentOrder) Fields() []ent.Field { Optional(). Nillable(). MaxLen(30), + field.JSON("provider_snapshot", map[string]any{}). + Optional(). + SchemaType(map[string]string{dialect.Postgres: "jsonb"}), // 状态 field.String("status"). diff --git a/backend/internal/handler/admin/payment_handler.go b/backend/internal/handler/admin/payment_handler.go index b0ed6aed..84359cd9 100644 --- a/backend/internal/handler/admin/payment_handler.go +++ b/backend/internal/handler/admin/payment_handler.go @@ -3,6 +3,7 @@ package admin import ( "strconv" + dbent "github.com/Wei-Shaw/sub2api/ent" "github.com/Wei-Shaw/sub2api/internal/pkg/response" "github.com/Wei-Shaw/sub2api/internal/service" @@ -66,7 +67,7 @@ func (h *PaymentHandler) ListOrders(c *gin.Context) { response.ErrorFrom(c, err) return } - response.Paginated(c, orders, int64(total), page, pageSize) + response.Paginated(c, sanitizeAdminPaymentOrdersForResponse(orders), int64(total), page, pageSize) } // GetOrderDetail returns detailed information about a single order. @@ -82,7 +83,7 @@ func (h *PaymentHandler) GetOrderDetail(c *gin.Context) { return } auditLogs, _ := h.paymentService.GetOrderAuditLogs(c.Request.Context(), orderID) - response.Success(c, gin.H{"order": order, "auditLogs": auditLogs}) + response.Success(c, gin.H{"order": sanitizeAdminPaymentOrderForResponse(order), "auditLogs": auditLogs}) } // CancelOrder cancels a pending order (admin). @@ -114,6 +115,26 @@ func (h *PaymentHandler) RetryFulfillment(c *gin.Context) { response.Success(c, gin.H{"message": "fulfillment retried"}) } +func sanitizeAdminPaymentOrdersForResponse(orders []*dbent.PaymentOrder) []*dbent.PaymentOrder { + if len(orders) == 0 { + return orders + } + out := make([]*dbent.PaymentOrder, 0, len(orders)) + for _, order := range orders { + out = append(out, sanitizeAdminPaymentOrderForResponse(order)) + } + return out +} + +func sanitizeAdminPaymentOrderForResponse(order *dbent.PaymentOrder) *dbent.PaymentOrder { + if order == nil { + return nil + } + cloned := *order + cloned.ProviderSnapshot = nil + return &cloned +} + // AdminProcessRefundRequest is the request body for admin refund processing. type AdminProcessRefundRequest struct { Amount float64 `json:"amount"` diff --git a/backend/internal/handler/payment_handler.go b/backend/internal/handler/payment_handler.go index 273aea73..0fba4726 100644 --- a/backend/internal/handler/payment_handler.go +++ b/backend/internal/handler/payment_handler.go @@ -6,6 +6,7 @@ import ( "strconv" "strings" + dbent "github.com/Wei-Shaw/sub2api/ent" "github.com/Wei-Shaw/sub2api/internal/payment" infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors" "github.com/Wei-Shaw/sub2api/internal/pkg/pagination" @@ -327,7 +328,7 @@ func (h *PaymentHandler) GetMyOrders(c *gin.Context) { response.ErrorFrom(c, err) return } - response.Paginated(c, orders, int64(total), page, pageSize) + response.Paginated(c, sanitizePaymentOrdersForResponse(orders), int64(total), page, pageSize) } // GetOrder returns a single order for the authenticated user. @@ -349,7 +350,7 @@ func (h *PaymentHandler) GetOrder(c *gin.Context) { response.ErrorFrom(c, err) return } - response.Success(c, order) + response.Success(c, sanitizePaymentOrderForResponse(order)) } // CancelOrder cancels a pending order for the authenticated user. @@ -445,7 +446,7 @@ func (h *PaymentHandler) VerifyOrder(c *gin.Context) { response.ErrorFrom(c, err) return } - response.Success(c, order) + response.Success(c, sanitizePaymentOrderForResponse(order)) } // PublicOrderResult is the limited order info returned by the public verify endpoint. @@ -523,6 +524,26 @@ func isMobile(c *gin.Context) bool { return false } +func sanitizePaymentOrdersForResponse(orders []*dbent.PaymentOrder) []*dbent.PaymentOrder { + if len(orders) == 0 { + return orders + } + out := make([]*dbent.PaymentOrder, 0, len(orders)) + for _, order := range orders { + out = append(out, sanitizePaymentOrderForResponse(order)) + } + return out +} + +func sanitizePaymentOrderForResponse(order *dbent.PaymentOrder) *dbent.PaymentOrder { + if order == nil { + return nil + } + cloned := *order + cloned.ProviderSnapshot = nil + return &cloned +} + func isWeChatBrowser(c *gin.Context) bool { return strings.Contains(strings.ToLower(c.GetHeader("User-Agent")), "micromessenger") } diff --git a/backend/internal/service/payment_order.go b/backend/internal/service/payment_order.go index 7d973b92..1f01bc11 100644 --- a/backend/internal/service/payment_order.go +++ b/backend/internal/service/payment_order.go @@ -73,7 +73,7 @@ func (s *PaymentService) CreateOrder(ctx context.Context, req CreateOrderRequest if oauthResp != nil { return oauthResp, nil } - order, err := s.createOrderInTx(ctx, req, user, plan, cfg, orderAmount, limitAmount, feeRate, payAmount) + order, err := s.createOrderInTx(ctx, req, user, plan, cfg, orderAmount, limitAmount, feeRate, payAmount, sel) if err != nil { return nil, err } @@ -122,7 +122,7 @@ func (s *PaymentService) validateSubOrder(ctx context.Context, req CreateOrderRe return plan, nil } -func (s *PaymentService) createOrderInTx(ctx context.Context, req CreateOrderRequest, user *User, plan *dbent.SubscriptionPlan, cfg *PaymentConfig, orderAmount, limitAmount, feeRate, payAmount float64) (*dbent.PaymentOrder, error) { +func (s *PaymentService) createOrderInTx(ctx context.Context, req CreateOrderRequest, user *User, plan *dbent.SubscriptionPlan, cfg *PaymentConfig, orderAmount, limitAmount, feeRate, payAmount float64, sel *payment.InstanceSelection) (*dbent.PaymentOrder, error) { tx, err := s.entClient.Tx(ctx) if err != nil { return nil, fmt.Errorf("begin transaction: %w", err) @@ -139,6 +139,13 @@ func (s *PaymentService) createOrderInTx(ctx context.Context, req CreateOrderReq tm = defaultOrderTimeoutMin } exp := time.Now().Add(time.Duration(tm) * time.Minute) + providerSnapshot := buildPaymentOrderProviderSnapshot(sel) + selectedInstanceID := "" + selectedProviderKey := "" + if sel != nil { + selectedInstanceID = strings.TrimSpace(sel.InstanceID) + selectedProviderKey = strings.TrimSpace(sel.ProviderKey) + } b := tx.PaymentOrder.Create(). SetUserID(req.UserID). SetUserEmail(user.Email). @@ -159,6 +166,15 @@ func (s *PaymentService) createOrderInTx(ctx context.Context, req CreateOrderReq if req.SrcURL != "" { b.SetSrcURL(req.SrcURL) } + if selectedInstanceID != "" { + b.SetProviderInstanceID(selectedInstanceID) + } + if selectedProviderKey != "" { + b.SetProviderKey(selectedProviderKey) + } + if providerSnapshot != nil { + b.SetProviderSnapshot(providerSnapshot) + } if plan != nil { b.SetPlanID(plan.ID).SetSubscriptionGroupID(plan.GroupID).SetSubscriptionDays(psComputeValidityDays(plan.ValidityDays, plan.ValidityUnit)) } @@ -192,6 +208,35 @@ func (s *PaymentService) checkPendingLimit(ctx context.Context, tx *dbent.Tx, us return nil } +func buildPaymentOrderProviderSnapshot(sel *payment.InstanceSelection) map[string]any { + if sel == nil { + return nil + } + + snapshot := map[string]any{} + snapshot["schema_version"] = 1 + + instanceID := strings.TrimSpace(sel.InstanceID) + if instanceID != "" { + snapshot["provider_instance_id"] = instanceID + } + + providerKey := strings.TrimSpace(sel.ProviderKey) + if providerKey != "" { + snapshot["provider_key"] = providerKey + } + + paymentMode := strings.TrimSpace(sel.PaymentMode) + if paymentMode != "" { + snapshot["payment_mode"] = paymentMode + } + + if len(snapshot) == 1 { + return nil + } + return snapshot +} + func (s *PaymentService) checkDailyLimit(ctx context.Context, tx *dbent.Tx, userID int64, amount, limit float64) error { if limit <= 0 { return nil diff --git a/backend/internal/service/payment_order_provider_snapshot_test.go b/backend/internal/service/payment_order_provider_snapshot_test.go new file mode 100644 index 00000000..c75566bc --- /dev/null +++ b/backend/internal/service/payment_order_provider_snapshot_test.go @@ -0,0 +1,116 @@ +//go:build unit + +package service + +import ( + "context" + "strconv" + "testing" + + "github.com/Wei-Shaw/sub2api/internal/payment" + "github.com/stretchr/testify/require" +) + +func TestBuildPaymentOrderProviderSnapshot_ExcludesSensitiveConfig(t *testing.T) { + t.Parallel() + + sel := &payment.InstanceSelection{ + InstanceID: "12", + ProviderKey: payment.TypeWxpay, + SupportedTypes: "wxpay,wxpay_direct", + PaymentMode: "popup", + Config: map[string]string{ + "privateKey": "secret", + "apiV3Key": "secret-v3", + "appId": "wx-app-id", + }, + } + + snapshot := buildPaymentOrderProviderSnapshot(sel) + require.Equal(t, map[string]any{ + "schema_version": 1, + "provider_instance_id": "12", + "provider_key": payment.TypeWxpay, + "payment_mode": "popup", + }, snapshot) + require.NotContains(t, snapshot, "config") + require.NotContains(t, snapshot, "privateKey") + require.NotContains(t, snapshot, "apiV3Key") + require.NotContains(t, snapshot, "supported_types") + require.NotContains(t, snapshot, "instance_name") +} + +func TestCreateOrderInTx_WritesProviderSnapshot(t *testing.T) { + ctx := context.Background() + client := newPaymentConfigServiceTestClient(t) + + user, err := client.User.Create(). + SetEmail("snapshot@example.com"). + SetPasswordHash("hash"). + SetUsername("snapshot-user"). + Save(ctx) + require.NoError(t, err) + + instance, err := client.PaymentProviderInstance.Create(). + SetProviderKey(payment.TypeAlipay). + SetName("Primary Alipay"). + SetConfig(`{"secretKey":"do-not-copy"}`). + SetSupportedTypes("alipay,alipay_direct"). + SetPaymentMode("redirect"). + SetEnabled(true). + Save(ctx) + require.NoError(t, err) + + svc := &PaymentService{entClient: client} + order, err := svc.createOrderInTx( + ctx, + CreateOrderRequest{ + UserID: user.ID, + PaymentType: payment.TypeAlipay, + OrderType: payment.OrderTypeBalance, + ClientIP: "127.0.0.1", + SrcHost: "app.example.com", + }, + &User{ + ID: user.ID, + Email: user.Email, + Username: user.Username, + }, + nil, + &PaymentConfig{ + MaxPendingOrders: 3, + OrderTimeoutMin: 30, + }, + 88, + 88, + 0, + 88, + &payment.InstanceSelection{ + InstanceID: strconv.FormatInt(instance.ID, 10), + ProviderKey: payment.TypeAlipay, + SupportedTypes: "alipay,alipay_direct", + PaymentMode: "redirect", + Config: map[string]string{ + "secretKey": "do-not-copy", + }, + }, + ) + require.NoError(t, err) + require.Equal(t, strconv.FormatInt(instance.ID, 10), valueOrEmpty(order.ProviderInstanceID)) + require.Equal(t, payment.TypeAlipay, valueOrEmpty(order.ProviderKey)) + require.Equal(t, float64(1), order.ProviderSnapshot["schema_version"]) + require.Equal(t, strconv.FormatInt(instance.ID, 10), order.ProviderSnapshot["provider_instance_id"]) + require.Equal(t, payment.TypeAlipay, order.ProviderSnapshot["provider_key"]) + require.Equal(t, "redirect", order.ProviderSnapshot["payment_mode"]) + require.NotContains(t, order.ProviderSnapshot, "config") + require.NotContains(t, order.ProviderSnapshot, "secretKey") + require.NotContains(t, order.ProviderSnapshot, "supported_types") + require.NotContains(t, order.ProviderSnapshot, "instance_name") +} + +func valueOrEmpty(v *string) string { + if v == nil { + return "" + } + return *v +} diff --git a/backend/migrations/117_add_payment_order_provider_snapshot.sql b/backend/migrations/117_add_payment_order_provider_snapshot.sql new file mode 100644 index 00000000..56a5fe2d --- /dev/null +++ b/backend/migrations/117_add_payment_order_provider_snapshot.sql @@ -0,0 +1,2 @@ +ALTER TABLE payment_orders +ADD COLUMN IF NOT EXISTS provider_snapshot JSONB;