2026-07-30 18:04:34 +08:00
package user
import (
2026-09-13 00:57:32 +08:00
"errors"
"time"
2026-07-30 18:04:34 +08:00
"git.apinb.com/bsm-sdk/core/errcode"
"git.apinb.com/bsm-sdk/core/infra"
"git.apinb.com/heqiapp/platforms/backend/api/internal/impl"
2026-07-31 09:54:17 +08:00
common "git.apinb.com/heqiapp/platforms/backend/api/internal/logic/common"
2026-08-02 23:24:32 +08:00
"git.apinb.com/heqiapp/platforms/backend/api/internal/logic/payment"
2026-07-30 18:04:34 +08:00
"git.apinb.com/heqiapp/platforms/backend/api/internal/models"
"github.com/gin-gonic/gin"
2026-09-13 00:57:32 +08:00
"gorm.io/gorm"
"gorm.io/gorm/clause"
2026-07-30 18:04:34 +08:00
)
// ServiceRelation 返回当前唯一有效服务归属的公开 identity。
func ServiceRelation ( ctx * gin . Context ) {
2026-07-31 09:54:17 +08:00
account , ok := common . UserAccount ( ctx )
2026-07-30 18:04:34 +08:00
if ! ok {
return
}
var relation models . UserServiceRelation
2026-07-31 09:54:17 +08:00
if impl . DBService . Where ( "user_account_id = ? AND status = ?" , account . ID , common . StatusEnable ) . First ( & relation ) . Error != nil {
2026-07-30 18:04:34 +08:00
infra . Response . Success ( ctx , nil )
return
}
response := gin . H { }
if relation . GasBasicID != 0 {
var gas models . GasBasic
if impl . DBService . First ( & gas , relation . GasBasicID ) . Error == nil {
response [ "gas_identity" ] , response [ "gas_name" ] = gas . Identity , gas . Name
}
}
if relation . DeliveryBasicID != 0 {
var delivery models . DeliveryBasic
if impl . DBService . First ( & delivery , relation . DeliveryBasicID ) . Error == nil {
response [ "delivery_identity" ] , response [ "delivery_name" ] = delivery . Identity , delivery . Name
}
}
infra . Response . Success ( ctx , response )
}
2026-09-13 00:57:32 +08:00
// PayGasOrder 为本人未履约供气订单创建余额或第三方支付单。
2026-08-02 23:24:32 +08:00
func PayGasOrder ( ctx * gin . Context ) {
account , ok := common . UserAccount ( ctx )
if ! ok {
return
}
var request struct {
2026-09-13 00:57:32 +08:00
PaymentPassword string ` json:"payment_password" `
RequestNo string ` json:"request_no" binding:"required" `
Channel string ` json:"channel" binding:"required,oneof=wallet alipay wechat" `
PayType string ` json:"pay_type" binding:"required" `
OpenID string ` json:"openid" `
2026-08-02 23:24:32 +08:00
}
if ctx . ShouldBindJSON ( & request ) != nil {
infra . Response . Error ( ctx , errcode . ErrInvalidArgument )
return
}
2026-09-13 00:57:32 +08:00
if request . Channel == "wallet" {
if err := payGasOrderWithWallet ( account , ctx . Param ( "identity" ) , request . RequestNo , request . PaymentPassword ) ; err != nil {
infra . Response . Error ( ctx , err )
return
}
infra . Response . Success ( ctx , gin . H { "paid" : true } )
return
}
2026-08-02 23:24:32 +08:00
var order models . GasorderBasic
if err := impl . DBService . Where ( "identity = ? AND user_account_id = ? AND order_status IN ?" , ctx . Param ( "identity" ) , account . ID , [ ] int { common . StatusCreated , common . StatusAssigned } ) . First ( & order ) . Error ; err != nil {
infra . Response . Error ( ctx , err )
return
}
payOrder , err := payment . Create ( ctx , payment . CreateInput { RequestNo : request . RequestNo , BusinessType : "gasorder" , BusinessIdentity : order . Identity ,
UserIdentity : account . Identity , Channel : request . Channel , PayType : request . PayType , Subject : "和气供气订单 " + order . OrderNo , OpenID : request . OpenID , Amount : order . PayableAmount } )
if err != nil {
infra . Response . Error ( ctx , err )
return
}
infra . Response . Success ( ctx , payment . PublicResponse ( payOrder ) )
}
2026-09-13 00:57:32 +08:00
// payGasOrderWithWallet 在同一事务内记录支付事实、扣减钱包并推进供气订单。
func payGasOrderWithWallet ( account models . UserAccount , identity , requestNo , password string ) error {
return impl . DBService . Transaction ( func ( tx * gorm . DB ) error {
var existing models . PaymentOrder
err := tx . Where ( "business_type = ? AND request_no = ?" , "gasorder" , requestNo ) . First ( & existing ) . Error
if err == nil {
if existing . BusinessIdentity != identity || existing . UserIdentity != account . Identity || existing . Channel != "wallet" || existing . PaymentStatus != payment . StatusPaid {
return errors . New ( "idempotency conflict" )
}
return nil
}
if ! errors . Is ( err , gorm . ErrRecordNotFound ) {
return err
}
var order models . GasorderBasic
if err := tx . Clauses ( clause . Locking { Strength : "UPDATE" } ) .
Where ( "identity = ? AND user_account_id = ? AND order_status IN ?" , identity , account . ID , [ ] int { common . StatusCreated , common . StatusAssigned } ) . First ( & order ) . Error ; err != nil {
return err
}
var wallet models . WalletBasic
if err := tx . Clauses ( clause . Locking { Strength : "UPDATE" } ) .
Where ( "owner_type = ? AND owner_identity = ?" , "user" , account . Identity ) . First ( & wallet ) . Error ; err != nil {
return err
}
if ! common . VerifyPaymentPassword ( account . Identity , wallet , password ) {
return gorm . ErrInvalidData
}
if err := common . SpendWalletBalance ( & wallet , order . PayableAmount ) ; err != nil {
return err
}
if err := common . SaveWalletBalances ( tx , wallet ) ; err != nil {
return err
}
now := time . Now ( )
if err := tx . Model ( & order ) . Update ( "order_status" , common . StatusPaid ) . Error ; err != nil {
return err
}
if err := payment . CompleteGasDeposits ( tx , order . Identity , now ) ; err != nil {
return err
}
if err := tx . Create ( & models . PaymentOrder {
Entity : common . NewEntity ( common . StatusEnable ) , PaymentStatus : payment . StatusPaid ,
PaymentNo : common . RecordNo ( "PAY" ) , RequestNo : requestNo , BusinessType : "gasorder" ,
BusinessIdentity : order . Identity , UserIdentity : account . Identity , MerchantIdentity : "platform" ,
Channel : "wallet" , PayType : "balance" , Amount : order . PayableAmount ,
Subject : "和气供气订单 " + order . OrderNo , ExpiresAt : now , PaidAt : & now ,
} ) . Error ; err != nil {
return err
}
date := now . In ( time . Local )
return tx . Create ( & models . WalletRecord {
Entity : common . NewEntity ( common . StatusEnable ) , WalletBasicID : wallet . ID , RecordNo : common . RecordNo ( "WR" ) ,
RequestNo : requestNo , Direction : "expense" , TradeType : "gasorder" , Amount : order . PayableAmount ,
BalanceAfter : wallet . Balance , WithdrawalBalanceAfter : wallet . WithdrawalBalance ,
OutTradeNo : order . OrderNo , PayChannel : "wallet" , OperatorIdentity : account . Identity ,
Ymd : int32 ( date . Year ( ) * 10000 + int ( date . Month ( ) ) * 100 + date . Day ( ) ) ,
Ym : int32 ( date . Year ( ) * 100 + int ( date . Month ( ) ) ) ,
} ) . Error
} )
}
2026-07-30 18:04:34 +08:00
// ListGasContracts 返回用户自己的供气合同。
func ListGasContracts ( ctx * gin . Context ) {
2026-07-31 09:54:17 +08:00
account , ok := common . UserAccount ( ctx )
2026-07-30 18:04:34 +08:00
if ! ok {
return
}
var list [ ] models . GasorderContract
2026-07-31 09:54:17 +08:00
if err := impl . DBService . Where ( "user_account_id = ? AND status <> ?" , account . ID , common . StatusArchived ) . Order ( "created_at desc" ) . Find ( & list ) . Error ; err != nil {
2026-07-30 18:04:34 +08:00
infra . Response . Error ( ctx , err )
return
}
2026-08-02 23:24:32 +08:00
response := make ( [ ] gin . H , 0 , len ( list ) )
2026-09-13 00:57:32 +08:00
contractIDs , stationIDs := [ ] uint64 { } , [ ] uint64 { }
for _ , contract := range list {
contractIDs = append ( contractIDs , contract . ID )
stationIDs = append ( stationIDs , contract . GasBasicID )
}
// 合同绑定属于合同,不得把合同主键误作订单主键读取气瓶。
var bindings [ ] models . GasorderContractProduct
var stations [ ] models . GasBasic
if len ( list ) > 0 {
if err := impl . DBService . Where ( "gasorder_contract_id IN ? AND status <> ?" , contractIDs , common . StatusArchived ) . Find ( & bindings ) . Error ; err != nil {
2026-08-02 23:24:32 +08:00
infra . Response . Error ( ctx , err )
return
}
2026-09-13 00:57:32 +08:00
if err := impl . DBService . Where ( "id IN ? AND status = ?" , stationIDs , common . StatusEnable ) . Find ( & stations ) . Error ; err != nil {
infra . Response . Error ( ctx , err )
return
}
}
stationNames := map [ uint64 ] string { }
for _ , station := range stations {
stationNames [ station . ID ] = station . Name
}
for _ , order := range list {
items := [ ] gin . H { }
for _ , binding := range bindings {
if binding . GasorderContractID == order . ID {
items = append ( items , gin . H { "identity" : binding . Identity , "product_code" : binding . ProductCode , "product_type_name" : binding . ProductTypeName , "unit_price" : binding . UnitPrice , "bound_at" : binding . BoundAt , "unbound_at" : binding . UnboundAt } )
}
}
2026-08-02 23:24:32 +08:00
value := common . ResourceResponse ( order ) . ( map [ string ] any )
2026-09-13 00:57:32 +08:00
// 旧字段保留空值以兼容客户端,私有附件不能以原始存储地址下发。
value [ "file_uri" ] = ""
value [ "has_attachment" ] = order . FileURI != ""
value [ "station_name" ] = stationNames [ order . GasBasicID ]
value [ "items" ] = items
2026-08-02 23:24:32 +08:00
response = append ( response , value )
}
infra . Response . Success ( ctx , response )
2026-07-30 18:04:34 +08:00
}
// ListGasOrders 返回用户自己的供气订单。
func ListGasOrders ( ctx * gin . Context ) {
2026-07-31 09:54:17 +08:00
account , ok := common . UserAccount ( ctx )
2026-07-30 18:04:34 +08:00
if ! ok {
return
}
2026-09-13 00:57:32 +08:00
query , page , ok := paginateClientList ( ctx , impl . DBService . Where ( "user_account_id = ? AND status <> ?" , account . ID , common . StatusArchived ) )
if ! ok {
return
}
2026-07-30 18:04:34 +08:00
var list [ ] models . GasorderBasic
2026-09-13 00:57:32 +08:00
if err := query . Order ( "created_at desc, identity" ) . Find ( & list ) . Error ; err != nil {
2026-07-30 18:04:34 +08:00
infra . Response . Error ( ctx , err )
return
}
2026-09-13 00:57:32 +08:00
stationIDs := make ( [ ] uint64 , 0 , len ( list ) )
seenStations := map [ uint64 ] struct { } { }
for _ , order := range list {
if _ , exists := seenStations [ order . GasBasicID ] ; ! exists {
seenStations [ order . GasBasicID ] = struct { } { }
stationIDs = append ( stationIDs , order . GasBasicID )
}
}
stationNames := map [ uint64 ] string { }
if len ( stationIDs ) > 0 {
var stations [ ] models . GasBasic
if err := impl . DBService . Where ( "id IN ? AND status = ?" , stationIDs , common . StatusEnable ) . Find ( & stations ) . Error ; err != nil {
infra . Response . Error ( ctx , err )
return
}
for _ , station := range stations {
stationNames [ station . ID ] = station . Name
}
}
response := make ( [ ] gin . H , 0 , len ( list ) )
for _ , order := range list {
var items [ ] models . GasorderItem
if err := impl . DBService . Where ( "gasorder_basic_id = ?" , order . ID ) . Find ( & items ) . Error ; err != nil {
infra . Response . Error ( ctx , err )
return
}
value := common . ResourceResponse ( order ) . ( map [ string ] any )
value [ "items" ] = common . ResourceResponse ( items )
value [ "station_name" ] = stationNames [ order . GasBasicID ]
value [ "status_code" ] , value [ "status_name" ] = order . OrderStatus , gasOrderStatusName ( order . OrderStatus )
value [ "allowed_actions" ] = gasOrderActions ( order . OrderStatus )
response = append ( response , value )
}
respondClientPage ( ctx , page , response )
2026-07-30 18:04:34 +08:00
}
// CancelGasOrder 仅允许取消已创建或已分派的本人订单。
func CancelGasOrder ( ctx * gin . Context ) {
2026-07-31 09:54:17 +08:00
account , ok := common . UserAccount ( ctx )
2026-07-30 18:04:34 +08:00
if ! ok {
return
}
2026-09-13 00:57:32 +08:00
err := impl . DBService . Transaction ( func ( tx * gorm . DB ) error {
var order models . GasorderBasic
if err := tx . Clauses ( clause . Locking { Strength : "UPDATE" } ) . Where ( "identity = ? AND user_account_id = ? AND order_status IN ?" , ctx . Param ( "identity" ) , account . ID , [ ] int { common . StatusCreated , common . StatusAssigned } ) . First ( & order ) . Error ; err != nil {
return err
}
if err := tx . Model ( & order ) . Updates ( map [ string ] any { "order_status" : common . StatusCancelled , "operator_identity" : account . Identity } ) . Error ; err != nil {
return err
}
if err := tx . Model ( & models . GasorderItem { } ) . Where ( "gasorder_basic_id = ?" , order . ID ) . Update ( "active" , false ) . Error ; err != nil {
return err
}
return tx . Model ( & models . GasorderDeposit { } ) . Where ( "gasorder_basic_id = ? AND deposit_status = ?" , order . ID , common . StatusPending ) . Update ( "deposit_status" , common . StatusCancelled ) . Error
} )
if err != nil {
2026-07-30 18:04:34 +08:00
infra . Response . Error ( ctx , errcode . ErrInvalidArgument )
return
}
infra . Response . Success ( ctx , gin . H { "cancelled" : true } )
}