package repositories import ( "context" "encoding/json" "errors" "fmt" "fusenapi/constants" "fusenapi/model/gmodel" "fusenapi/utils/basic" "fusenapi/utils/fssql" "fusenapi/utils/handlers" "fusenapi/utils/order" "fusenapi/utils/pay" "time" "github.com/aws/aws-sdk-go/aws/session" "github.com/stripe/stripe-go/v75" "github.com/zeromicro/go-zero/core/logc" "github.com/zeromicro/go-zero/core/logx" "gorm.io/gorm" ) func NewOrder(gormDB *gorm.DB, bLMServiceUrl *string, awsSession *session.Session) Order { return &defaultOrder{ MysqlConn: gormDB, } } type ( defaultOrder struct { MysqlConn *gorm.DB } Order interface { // 下单 Create(ctx context.Context, in *CreateReq) (res *CreateRes, err error) // 预支付--定金 CreatePrePaymentByDeposit(ctx context.Context, in *CreatePrePaymentByDepositReq) (res *CreatePrePaymentByDepositRes, err error) // 预支付--定金 CreatePrePaymentByBalance(ctx context.Context, in *CreatePrePaymentByBalanceReq) (res *CreatePrePaymentByBalanceRes, err error) // 列表 List(ctx context.Context, in *ListReq) (res *ListRes, err error) // 详情 Detail(ctx context.Context, in *DetailReq) (res *DetailRes, err error) // 支付成功 PaymentSuccessful(ctx context.Context, in *PaymentSuccessfulReq) (res *PaymentSuccessfulRes, err error) } PayInfo struct { PayMethod string `json:"pay_method"` // 交易方式 PayTime time.Time `json:"pay_time"` // 支付时间 Status gmodel.PayStatus `json:"status"` // 当前状态 StatusLink []gmodel.PayStatus `json:"status_link"` // 状态链路 TradeNo string `json:"trade_no"` // 支付交易号 } OrderAddress struct { Address string `json:"address"` // 详细地址 Mobile string `json:"mobile"` // 手机 Name string `json:"name"` // 姓名 } OrderPay struct { ClientSecret string `json:"client_secret"` // 支付方--秘钥 Country string `json:"country"` // 国家 Currency string `json:"currency"` // 货币 Metadata map[string]interface{} `json:"metadata"` // 额外参数 Method string `json:"method"` // 支付方--途径 OrderSn string `json:"order_sn"` // 订单编码 PayStage int64 `json:"pay_stage"` // 支付阶段 RedirectURL *string `json:"redirect_url"` // 支付方--重定向地址 Total OrderPayTotal `json:"total"` // 支付参数 } // 支付参数 OrderPayTotal struct { Amount int64 `json:"amount"` // 金额 Label string `json:"label"` // 标签 } /* 支付成功 */ PaymentSuccessfulReq struct { EventId string PaymentMethod string Charge *stripe.Charge } PaymentSuccessfulRes struct{} /* 支付成功 */ /* 预支付--定金 */ CreatePrePaymentByDepositReq struct { StripeKey string `json:"stripe_key"` Currency string `json:"currency"` Country string `json:"country"` UserId int64 `json:"user_id"` OrderSn string `json:"order_sn"` DeliveryMethod int64 `json:"delivery_method"` DeliveryAddress *OrderAddress `json:"delivery_address"` } CreatePrePaymentByDepositRes struct { ErrorCode basic.StatusResponse OrderDetail gmodel.OrderDetail OrderPay OrderPay } /* 预支付--定金 */ /* 预支付--尾款 */ CreatePrePaymentByBalanceReq struct { StripeKey string `json:"stripe_key"` Currency string `json:"currency"` Country string `json:"country"` UserId int64 `json:"user_id"` OrderSn string `json:"order_sn"` } CreatePrePaymentByBalanceRes struct { ErrorCode basic.StatusResponse OrderDetail gmodel.OrderDetail OrderPay OrderPay } /* 预支付--尾款 */ /* 下单 */ CreateReq struct { ExpectedDeliveryTime time.Time `json:"expected_delivery_time"` // 预计到货时间 ExchangeRate int64 `json:"exchange_rate"` // 换算汇率(厘) CurrentCurrency string `json:"current_currency"` // 当前货币 OriginalCurrency string `json:"original_currency"` // 原始货币 UserId int64 `json:"user_id"` CartIds []int64 `json:"cart_ids"` DeliveryMethod int64 `json:"delivery_method"` DeliveryAddress *OrderAddress `json:"delivery_address"` // 收货地址 } CreateRes struct { ErrorCode basic.StatusResponse OrderSn string } /* 下单 */ /* 详情 */ DetailReq struct { UserId int64 `json:"user_id"` OrderSn string `json:"order_sn"` } DetailRes struct { ErrorCode basic.StatusResponse OrderDetail gmodel.OrderDetail OrderDetailOriginal gmodel.OrderDetail } /* 详情 */ /* 列表 */ ListReq struct { UserId int64 `json:"user_id"` DeliveryMethod int64 `json:"delivery_method"` OrderCycle string `json:"order_cycle"` CurrentPage int64 `json:"current_page"` PerPage int64 `json:"per_page"` } ListRes struct { OrderDetailList []gmodel.OrderDetail Meta interface{} } /* 列表 */ ) // 支付成功 func (d *defaultOrder) PaymentSuccessful(ctx context.Context, in *PaymentSuccessfulReq) (res *PaymentSuccessfulRes, err error) { var orderSn string var payStage string var ok bool var card string var brand string var country string var currency string var tradeSn string var payAmount int64 var payTitle string var payTime time.Time var paymentMethod int64 = 1 if in.PaymentMethod == "stripe" { paymentMethod = 1 } if in.PaymentMethod == "paypal" { paymentMethod = 2 } if in.Charge != nil { charge := in.Charge orderSn, ok = charge.Metadata["order_sn"] if !ok || orderSn == "" { err = errors.New("order_sn is empty") logc.Errorf(ctx, "PaymentSuccessful failed param, eventId:%s,err:%v", in.EventId, err) return &PaymentSuccessfulRes{}, err } payStage, ok = charge.Metadata["pay_stage"] if !ok || payStage == "" { err = errors.New("pay_stage is empty") logc.Errorf(ctx, "PaymentSuccessful failed param, eventId:%s,err:%v", in.EventId, err) return &PaymentSuccessfulRes{}, err } country, ok = charge.Metadata["country"] if !ok || country == "" { err = errors.New("country is empty") logc.Errorf(ctx, "PaymentSuccessful failed param, eventId:%s,err:%v", in.EventId, err) return &PaymentSuccessfulRes{}, err } if charge.PaymentMethodDetails != nil { if charge.PaymentMethodDetails.Card != nil { if charge.PaymentMethodDetails.Card.Last4 != "" { card = charge.PaymentMethodDetails.Card.Last4 } if charge.PaymentMethodDetails.Card.Brand != "" { brand = string(charge.PaymentMethodDetails.Card.Brand) } } } if charge.Currency != "" { currency = string(charge.Currency) } tradeSn = charge.ID payAmount = charge.Amount payTitle = charge.Description payTime = time.Unix(charge.Created, 0) } err = d.MysqlConn.WithContext(ctx).Transaction(func(tx *gorm.DB) error { var orderInfo gmodel.FsOrder result := tx.Where("is_del = ?", 0).Where("order_sn = ?", orderSn).Take(&orderInfo) err = result.Error if err != nil { logx.Errorf("PaymentSuccessful failed order Take, eventId:%s,err: %v", in.EventId, err) return err } ress, err := d.OrderDetailHandler(ctx, &orderInfo, 0) if err != nil { logx.Errorf("PaymentSuccessful failed DetailOrderDetailHandler,eventId:%s, err: %v", in.EventId, err) return err } var ntime = time.Now().UTC() if (payStage == "deposit" && *orderInfo.PayStatus == int64(constants.ORDERPAYSTATUSUNPAIDDEPOSIT)) || (payStage == "remaining_balance" && *orderInfo.PayStatus == int64(constants.ORDERPAYSTATUSPAIDDEPOSIT)) { var payStatus = int64(constants.PAYSTATUSPAID) var payStageInt int64 var orderPayStatusCode constants.OrderPayStatusCode // 订单状态--当前 var status gmodel.OrderStatus var statusLink []gmodel.OrderStatus var uOrderDetail = make(map[string]interface{}) var payInfo PayInfo payInfo.PayMethod = in.PaymentMethod payInfo.PayTime = payTime payInfo.TradeNo = tradeSn // 当前状态 var statusCode constants.OrderStatusCode var statusCodePre constants.OrderStatusCode if payStage == "deposit" { if *orderInfo.DeliveryMethod == constants.DELIVERYMETHODDIRECTMAIL { // 直邮 statusCode = constants.ORDERSTATUSDIRECTMAILORDERED } if *orderInfo.DeliveryMethod == constants.DELIVERYMETHODDSCLOUDSTORE { // 云仓 statusCode = constants.ORDERSTATUSCLOUDSTOREORDERED } payStageInt = 1 orderPayStatusCode = constants.ORDERPAYSTATUSPAIDDEPOSIT status = gmodel.OrderStatus{ Ctime: &ntime, Utime: &ntime, StatusCode: statusCode, StatusTitle: constants.OrderStatusMessage[statusCode], } statusLink = order.UpdateOrderStatusLink(ress.OrderDetailOriginal.OrderInfo.StatusLink, gmodel.OrderStatus{ Ctime: &ntime, Utime: &ntime, StatusCode: statusCode, StatusTitle: constants.OrderStatusMessage[statusCode], }) payInfo.Status = gmodel.PayStatus{ StatusCode: int64(constants.PAYSTATUSPAID), StatusTitle: constants.PayStatusMessage[constants.PAYSTATUSPAID], } payInfo.StatusLink = append(ress.OrderDetail.OrderAmount.Deposit.StatusLink, payInfo.Status) uOrderDetail["order_amount"] = struct { Deposit PayInfo `json:"deposit"` }{ Deposit: payInfo, } } if payStage == "remaining_balance" { if *orderInfo.DeliveryMethod == constants.DELIVERYMETHODDIRECTMAIL { // 直邮 statusCodePre = constants.ORDERSTATUSDIRECTMAILORDERED statusCode = constants.ORDERSTATUSDIRECTMAILORDEREDMAINING } if *orderInfo.DeliveryMethod == constants.DELIVERYMETHODDSCLOUDSTORE { // 云仓 statusCodePre = constants.ORDERSTATUSCLOUDSTOREORDERED statusCode = constants.ORDERSTATUSCLOUDSTOREORDEREDMAINING } payStageInt = 2 orderPayStatusCode = constants.ORDERPAYSTATUSPAIDDREMAINING var statusChildren []*gmodel.OrderStatus // 更新订单状态链路--子状态 for oStatusLinkKey, oStatusLink := range ress.OrderDetail.OrderInfo.StatusLink { if oStatusLink.StatusCode == statusCodePre { statusChildren = append(oStatusLink.Children, &gmodel.OrderStatus{ Ctime: &ntime, Utime: &ntime, StatusCode: statusCode, StatusTitle: constants.OrderStatusMessage[statusCode], }) ress.OrderDetail.OrderInfo.StatusLink[oStatusLinkKey].Children = statusChildren } } if ress.OrderDetail.OrderInfo.Status.StatusCode == constants.ORDERSTATUSDIRECTMAILORDERED || ress.OrderDetail.OrderInfo.Status.StatusCode == constants.ORDERSTATUSCLOUDSTOREORDERED { status = ress.OrderDetail.OrderInfo.Status status.Children = statusChildren } payInfo.StatusLink = append(ress.OrderDetail.OrderAmount.RemainingBalance.StatusLink, payInfo.Status) uOrderDetail["order_amount"] = struct { RemainingBalance PayInfo `json:"remaining_balance"` }{ RemainingBalance: payInfo, } } // 新增交易信息 tx.Create(&gmodel.FsOrderTrade{ UserId: orderInfo.UserId, OrderSn: &orderSn, OrderSource: orderInfo.OrderSource, TradeSn: &tradeSn, PayAmount: &payAmount, PayStatus: &payStatus, PaymentMethod: &paymentMethod, PayStage: &payStageInt, CardSn: &card, CardBrand: &brand, Country: &country, Currency: ¤cy, Ctime: &ntime, Utime: &ntime, PayTitle: &payTitle, }) var sql string if *orderInfo.Status == int64(constants.ORDERSTATUSUNPAIDDEPOSIT) { sql = fmt.Sprintf(", `utime` = '%s', `pay_status` = %d, `status` = %d ", ntime, orderPayStatusCode, statusCode) } else { sql = fmt.Sprintf(", `utime` = '%s', `pay_status` = %d", ntime, orderPayStatusCode) } // 更新订单信息 uOrderDetail["order_info"] = struct { Utime *time.Time `json:"utime"` Status gmodel.OrderStatus `json:"status"` StatusLink []gmodel.OrderStatus `json:"status_link"` }{ Utime: &ntime, Status: status, StatusLink: statusLink, } if len(uOrderDetail) > 0 { err = fssql.MetadataOrderPATCH(d.MysqlConn, sql, orderSn, gmodel.FsOrder{}, uOrderDetail, "id = ?", orderInfo.Id) if err != nil { logx.Errorf("PaymentSuccessful failed MetadataOrderPATCH,eventId:%s, err: %v", in.EventId, err) return err } } } return nil }) return &PaymentSuccessfulRes{}, nil } // 预支付--尾款 func (d *defaultOrder) CreatePrePaymentByBalance(ctx context.Context, in *CreatePrePaymentByBalanceReq) (res *CreatePrePaymentByBalanceRes, err error) { var errorCode basic.StatusResponse var order gmodel.FsOrder model := d.MysqlConn.Where("is_del = ?", 0) if in.UserId != 0 { model.Where("user_id = ?", in.UserId) } if in.OrderSn != "" { model.Where("order_sn = ?", in.OrderSn) } result := model.Take(&order) if result.Error != nil { if errors.Is(result.Error, gorm.ErrRecordNotFound) { errorCode = *basic.CodeErrOrderCreatePrePaymentInfoNoFound } else { errorCode = *basic.CodeServiceErr } logx.Errorf("create prePayment balance failed, err: %v", err) return &CreatePrePaymentByBalanceRes{ ErrorCode: errorCode, }, result.Error } // 非未支付 if *order.PayStatus != int64(constants.ORDERPAYSTATUSPAIDDEPOSIT) { errorCode = *basic.CodeErrOrderCreatePrePaymentNoUnPaid err = errors.New("order balance pay status is not unPaid") logx.Errorf("create prePayment balance failed, err: %v", err) return &CreatePrePaymentByBalanceRes{ ErrorCode: errorCode, }, err } ress, err := d.OrderDetailHandler(ctx, &order, 1) if err != nil { logx.Errorf("create prePayment balance failed DetailOrderDetailHandler, err: %v", err) errorCode = *basic.CodeServiceErr return &CreatePrePaymentByBalanceRes{ ErrorCode: errorCode, }, err } // 支付初始化 amount := int64(ress.OrderDetailOriginal.OrderAmount.RemainingBalance.PayAmount.Current.CurrentAmount.(float64) / float64(10)) payConfig := &pay.Config{} payConfig.Stripe.PayType = "intent" payConfig.Stripe.Key = in.StripeKey var metadata = make(map[string]string, 2) metadata["model"] = "product_order" metadata["order_sn"] = in.OrderSn metadata["pay_stage"] = "remaining_balance" metadata["country"] = in.Country var generatePrepaymentReq = &pay.GeneratePrepaymentReq{ Metadata: metadata, ProductName: "支付尾款后期统一调整", Amount: amount, Currency: "usd", Quantity: 1, ProductDescription: "支付尾款后期统一调整", } payDriver := pay.NewPayDriver(1, payConfig) prepaymentRes, err := payDriver.GeneratePrepayment(generatePrepaymentReq) if err != nil { logx.Errorf("create prePayment balance failed GeneratePrepayment, err: %v", err) errorCode = *basic.CodeServiceErr return &CreatePrePaymentByBalanceRes{ ErrorCode: errorCode, }, nil } return &CreatePrePaymentByBalanceRes{ OrderDetail: ress.OrderDetail, OrderPay: OrderPay{ ClientSecret: prepaymentRes.ClientSecret, Country: in.Country, Currency: in.Currency, Method: payConfig.Stripe.PayType, OrderSn: in.OrderSn, PayStage: 1, Total: OrderPayTotal{ Amount: amount, Label: "支付尾款后期统一调整", }, }, }, nil } // 预支付--定金 func (d *defaultOrder) CreatePrePaymentByDeposit(ctx context.Context, in *CreatePrePaymentByDepositReq) (res *CreatePrePaymentByDepositRes, err error) { var errorCode basic.StatusResponse var order gmodel.FsOrder model := d.MysqlConn.Where("is_del = ?", 0) if in.UserId != 0 { model.Where("user_id = ?", in.UserId) } if in.OrderSn != "" { model.Where("order_sn = ?", in.OrderSn) } result := model.Take(&order) if result.Error != nil { if errors.Is(result.Error, gorm.ErrRecordNotFound) { errorCode = *basic.CodeErrOrderCreatePrePaymentInfoNoFound } else { errorCode = *basic.CodeServiceErr } logx.Errorf("create prePayment deposit failed, err: %v", err) return &CreatePrePaymentByDepositRes{ ErrorCode: errorCode, }, result.Error } // 非未支付 if *order.PayStatus != int64(constants.ORDERPAYSTATUSUNPAIDDEPOSIT) { errorCode = *basic.CodeErrOrderCreatePrePaymentNoUnPaid err = errors.New("order pay status is not unPaidDeposit") logx.Errorf("create prePayment deposit failed, err: %v", err) return &CreatePrePaymentByDepositRes{ ErrorCode: errorCode, }, err } // 是否超时支付 ntime := time.Now().UTC() ctime := *order.Ctime ctimeTimeOut := ctime.Add(30 * time.Minute).UTC().Unix() ntimeTimeOut := ntime.Unix() // 测试超时支付不限制 if ctimeTimeOut == ntimeTimeOut { errorCode = *basic.CodeErrOrderCreatePrePaymentTimeout err = errors.New("order pay timeout") logx.Errorf("create prePayment deposit failed, err: %v", err) return &CreatePrePaymentByDepositRes{ ErrorCode: errorCode, }, err } ress, err := d.OrderDetailHandler(ctx, &order, 0) if err != nil { logx.Errorf("create prePayment deposit failed DetailOrderDetailHandler, err: %v", err) errorCode = *basic.CodeServiceErr return &CreatePrePaymentByDepositRes{ ErrorCode: errorCode, }, err } var uOrderDetail = make(map[string]interface{}) var orderAddress *gmodel.OrderAddress if in.DeliveryMethod == constants.DELIVERYMETHODDIRECTMAIL { orderAddress = &gmodel.OrderAddress{ Name: in.DeliveryAddress.Name, Mobile: in.DeliveryAddress.Mobile, Address: in.DeliveryAddress.Address, } } var sql string = fmt.Sprintf(", `utime` = '%s'", ntime) // 是否更新 if in.DeliveryMethod != *order.DeliveryMethod { sql = sql + fmt.Sprintf(",`delivery_method` = %d", in.DeliveryMethod) if in.DeliveryMethod == constants.DELIVERYMETHODDIRECTMAIL { uOrderDetail["delivery_address"] = orderAddress } else { uOrderDetail["delivery_address"] = nil } uOrderDetail["order_info"] = struct { DeliveryMethod int64 `json:"delivery_method"` Utime *time.Time `json:"utime"` }{ DeliveryMethod: in.DeliveryMethod, Utime: &ntime, } } else { if in.DeliveryMethod == constants.DELIVERYMETHODDIRECTMAIL { // 更新收货地址 if orderAddress != nil { uOrderDetail["delivery_address"] = orderAddress uOrderDetail["order_info"] = struct { DeliveryMethod int64 `json:"delivery_method"` Utime *time.Time `json:"utime"` }{ DeliveryMethod: in.DeliveryMethod, Utime: &ntime, } } } } if len(uOrderDetail) > 0 { err = fssql.MetadataOrderPATCH(d.MysqlConn, sql, in.OrderSn, gmodel.FsOrder{}, uOrderDetail, "id = ?", order.Id) if err != nil { errorCode = *basic.CodeServiceErr logx.Errorf("create prePayment deposit failed MetadataOrderPATCH, err: %v", err) return &CreatePrePaymentByDepositRes{ ErrorCode: errorCode, }, result.Error } } ress.OrderDetail.OrderInfo.Utime = &ntime ress.OrderDetail.OrderInfo.DeliveryMethod = in.DeliveryMethod ress.OrderDetail.DeliveryAddress = orderAddress // 支付初始化 amount := int64(ress.OrderDetailOriginal.OrderAmount.Deposit.PayAmount.Current.CurrentAmount.(float64) / float64(10)) payConfig := &pay.Config{} payConfig.Stripe.PayType = "intent" payConfig.Stripe.Key = in.StripeKey var metadata = make(map[string]string, 2) metadata["model"] = "product_order" metadata["order_sn"] = in.OrderSn metadata["pay_stage"] = "deposit" metadata["country"] = in.Country var generatePrepaymentReq = &pay.GeneratePrepaymentReq{ Metadata: metadata, ProductName: "支付首款", Amount: amount, Currency: "usd", Quantity: 1, ProductDescription: "支付首款", } payDriver := pay.NewPayDriver(1, payConfig) prepaymentRes, err := payDriver.GeneratePrepayment(generatePrepaymentReq) if err != nil { logx.Errorf("create prePayment deposit failed GeneratePrepayment, err: %v", err) errorCode = *basic.CodeServiceErr return &CreatePrePaymentByDepositRes{ ErrorCode: errorCode, }, nil } return &CreatePrePaymentByDepositRes{ OrderDetail: ress.OrderDetail, OrderPay: OrderPay{ ClientSecret: prepaymentRes.ClientSecret, Country: in.Country, Currency: in.Currency, Method: payConfig.Stripe.PayType, OrderSn: in.OrderSn, PayStage: 1, Total: OrderPayTotal{ Amount: amount, Label: "", }, }, }, nil } // 列表 func (d *defaultOrder) List(ctx context.Context, in *ListReq) (res *ListRes, err error) { var orderList []gmodel.FsOrder model := d.MysqlConn.Model(&gmodel.FsOrder{}).Where("is_del = ?", 0) if in.UserId != 0 { model.Where("user_id = ?", in.UserId) } // Where("pay_status > ?", 0) if in.DeliveryMethod != 0 { model.Where("delivery_method = ?", in.DeliveryMethod) } if in.OrderCycle != "" { // 下单时间 switch in.OrderCycle { case "within_one_month": model.Where("ctime >?", time.Now().UTC().AddDate(0, -1, 0).Unix()) case "within_three_month": model.Where("ctime >?", time.Now().UTC().AddDate(0, -3, 0).Unix()) case "within_six_month": model.Where("ctime >?", time.Now().UTC().AddDate(0, -6, 0).Unix()) case "within_one_year": model.Where("ctime >?", time.Now().UTC().AddDate(-1, 0, 0).Unix()) } } var count int64 resultCount := model.Count(&count) if resultCount.Error != nil { logx.Errorf("order count failed, err: %v", err) return nil, resultCount.Error } var orderDetailList []gmodel.OrderDetail if count > 0 { m := model.Scopes(handlers.Paginate(&in.CurrentPage, &in.PerPage)) result := m.Find(&orderList) if result.Error != nil { logx.Errorf("order list failed, err: %v", err) return nil, result.Error } for _, order := range orderList { ress, err := d.OrderDetailHandler(ctx, &order, 1) if err != nil { return nil, err } orderDetailList = append(orderDetailList, ress.OrderDetail) } } else { orderDetailList = make([]gmodel.OrderDetail, 0) } return &ListRes{ OrderDetailList: orderDetailList, Meta: map[string]int64{ "total_count": count, "page_count": count / in.PerPage, "current_page": in.CurrentPage, "per_page": in.PerPage, }, }, nil } // 详情 func (d *defaultOrder) Detail(ctx context.Context, in *DetailReq) (res *DetailRes, err error) { var errorCode basic.StatusResponse var order gmodel.FsOrder model := d.MysqlConn.Where("is_del = ?", 0) if in.UserId != 0 { model.Where("user_id = ?", in.UserId) } if in.OrderSn != "" { model.Where("order_sn = ?", in.OrderSn) } result := model.Take(&order) if result.Error != nil { if errors.Is(result.Error, gorm.ErrRecordNotFound) { errorCode = *basic.CodeErrOrderCreatePrePaymentInfoNoFound } else { errorCode = *basic.CodeServiceErr } logx.Errorf("order detail failed, err: %v", err) return &DetailRes{ ErrorCode: errorCode, }, result.Error } // 是否超时支付 ctime := *order.Ctime ctimeTimeOut := ctime.Add(30 * time.Minute).UTC().Unix() ntimeTimeOut := time.Now().UTC().Unix() if ctimeTimeOut < ntimeTimeOut { errorCode = *basic.CodeErrOrderCreatePrePaymentTimeout err = errors.New("order pay timeout") logx.Errorf("order detail failed, err: %v", err) return &DetailRes{ ErrorCode: errorCode, }, err } ress, err := d.OrderDetailHandler(ctx, &order, 1) if err != nil { logx.Errorf("order detail failed, err: %v", err) errorCode = *basic.CodeServiceErr return &DetailRes{ ErrorCode: errorCode, }, err } return &DetailRes{ ErrorCode: errorCode, OrderDetail: ress.OrderDetail, }, nil } // 下单 func (d *defaultOrder) Create(ctx context.Context, in *CreateReq) (res *CreateRes, err error) { var errorCode basic.StatusResponse // 订单编号 var orderSn string = order.GenerateOrderNumber() err = d.MysqlConn.WithContext(ctx).Transaction(func(tx *gorm.DB) error { // 查询购物车 var shoppingCartList []*gmodel.RelaFsShoppingCart resShoppingCartFind := tx.Table(gmodel.NewFsShoppingCartModel(tx).TableName()). Preload("ShoppingCartProduct", func(dbPreload *gorm.DB) *gorm.DB { return dbPreload.Table(gmodel.NewFsProductModel(tx).TableName()).Preload("CoverResource") }). Preload("ShoppingCartProductModel3dList"). Preload("ShoppingCartProductModel3dFitting"). Where("id IN ?", in.CartIds). Where("user_id = ?", in.UserId). Find(&shoppingCartList) err = resShoppingCartFind.Error if err != nil { return err } shoppingCartListLen := len(shoppingCartList) if shoppingCartListLen == 0 { errorCode = *basic.CodeErrOrderCreatShoppingCartEmpty return errors.New(errorCode.Message) } if shoppingCartListLen != len(in.CartIds) { errorCode = *basic.CodeErrOrderCreatShoppingCartNotMatched return errors.New(errorCode.Message) } // 订单商品列表 var orderProductList []gmodel.OrderProduct var shippingFee gmodel.AmountInfo // 订单税费总价(厘) var shippingFeeTotal int64 var tax = gmodel.AmountInfo{} // 订单邮费总价(厘) var taxTotal int64 var discount gmodel.AmountInfo // 订单折扣总价(厘) var discountTotal int64 var subtotal gmodel.AmountInfo // 订单商品总价(厘) var orderProductTotal int64 var total gmodel.AmountInfo // 订单总价(厘) var orderTotal int64 var nowTime = time.Now().UTC() // 收货地址 var orderAddress *gmodel.OrderAddress // 支付状态 var payStatus = constants.ORDERPAYSTATUSUNPAIDDEPOSIT // 直邮 if in.DeliveryMethod == constants.DELIVERYMETHODDIRECTMAIL { orderAddress = &gmodel.OrderAddress{ Mobile: in.DeliveryAddress.Mobile, Name: in.DeliveryAddress.Name, } } for _, shoppingCart := range shoppingCartList { // 购物车快照 var shoppingCartSnapshot gmodel.CartSnapshot // 购物车商品模型 var shoppingCartProductModel3d *gmodel.FsProductModel3d if shoppingCart.Snapshot != nil { json.Unmarshal([]byte(*shoppingCart.Snapshot), &shoppingCartSnapshot) } // 商品异常 if shoppingCart.ShoppingCartProduct == nil || (shoppingCart.ShoppingCartProduct != nil && *shoppingCart.ShoppingCartProduct.IsShelf == 0) { errorCode = *basic.CodeErrOrderCreatProductAbsent errorCode.Message = "create order failed, product '" + shoppingCartSnapshot.ProductInfo.ProductName + "'is absent" return errors.New(errorCode.Message) } // 商品模型异常 if len(shoppingCart.ShoppingCartProductModel3dList) == 0 { errorCode = *basic.CodeErrOrderCreatProductAccessoryAbsent errorCode.Message = "create order failed, accessoryof product '" + shoppingCartSnapshot.ProductInfo.ProductName + "'is absent" return errors.New(errorCode.Message) } else { var isProductModel bool for _, shoppingCartProductModel3dInfo := range shoppingCart.ShoppingCartProductModel3dList { if *shoppingCart.SizeId == *shoppingCartProductModel3dInfo.SizeId { shoppingCartProductModel3d = shoppingCartProductModel3dInfo isProductModel = true break } } if !isProductModel { errorCode = *basic.CodeErrOrderCreatProductAccessoryAbsent errorCode.Message = "create order failed, accessory of product '" + shoppingCartSnapshot.ProductInfo.ProductName + "'is absent" return errors.New(errorCode.Message) } shoppingCart.ShoppingCartProductModel3dList = []*gmodel.FsProductModel3d{shoppingCartProductModel3d} } var stepPriceJson gmodel.StepPriceJsonStruct if shoppingCartProductModel3d.StepPrice != nil && len(*shoppingCartProductModel3d.StepPrice) != 0 { err = json.Unmarshal(*shoppingCartProductModel3d.StepPrice, &shoppingCartProductModel3d.StepPrice) if err != nil { return err } } else { errorCode = *basic.CodeErrOrderCreatProductPriceAbsent errorCode.Message = "create order failed, step price of product '" + shoppingCartSnapshot.ProductInfo.ProductName + "'is failed" return errors.New("shoppingCartProductModel3d.StepPrice nil") } /* 计算价格 */ productTotalPrice, productPrice, err := NewShoppingCart(tx, nil, nil).CaculateStepPrice(*shoppingCart.PurchaseQuantity, stepPriceJson, *shoppingCart.ShoppingCartProductModel3dFitting.Price) if err != nil { errorCode = *basic.CodeErrOrderCreatProductPriceAbsent errorCode.Message = "create order failed, step price of product '" + shoppingCartSnapshot.ProductInfo.ProductName + "'is failed" return err } /* 计算价格 */ // 订单商品总价(厘) orderProductTotal = orderProductTotal + productTotalPrice // 订单商品 var productCoverMetadata map[string]interface{} if shoppingCart.ShoppingCartProduct.CoverResource != nil && shoppingCart.ShoppingCartProduct.CoverResource.Metadata != nil { json.Unmarshal(*shoppingCart.ShoppingCartProduct.CoverResource.Metadata, &productCoverMetadata) } snapshot, err := d.OrderDetailSnapshotHandler(ctx, shoppingCart.FsShoppingCart.Snapshot) if err != nil { return err } var shoppingCartSnapshotInter = &gmodel.FsShoppingCartData{ Id: shoppingCart.FsShoppingCart.Id, UserId: shoppingCart.FsShoppingCart.UserId, ProductId: shoppingCart.FsShoppingCart.ProductId, TemplateId: shoppingCart.FsShoppingCart.TemplateId, ModelId: shoppingCart.FsShoppingCart.ModelId, SizeId: shoppingCart.FsShoppingCart.SizeId, LightId: shoppingCart.FsShoppingCart.LightId, FittingId: shoppingCart.FsShoppingCart.FittingId, PurchaseQuantity: shoppingCart.FsShoppingCart.PurchaseQuantity, Snapshot: &snapshot, SnapshotData: shoppingCart.FsShoppingCart.Snapshot, IsSelected: shoppingCart.FsShoppingCart.IsSelected, IsHighlyCustomized: shoppingCart.FsShoppingCart.IsHighlyCustomized, Ctime: shoppingCart.FsShoppingCart.Ctime, Utime: shoppingCart.FsShoppingCart.Utime, } var purchaseQuantityInter = gmodel.PurchaseQuantity{ Current: *shoppingCart.PurchaseQuantity, Initiate: *shoppingCart.PurchaseQuantity, } productInter := gmodel.OrderProduct{ TotalPrice: order.GetAmountInfo(order.GetAmountInfoReq{ ExchangeRate: in.ExchangeRate, Initiate: productTotalPrice, Current: productTotalPrice, CurrentCurrency: in.CurrentCurrency, OriginalCurrency: in.OriginalCurrency, }), ExpectedDeliveryTime: &in.ExpectedDeliveryTime, PurchaseQuantity: purchaseQuantityInter, ProductID: *shoppingCart.ProductId, ProductCover: *shoppingCart.ShoppingCartProduct.Cover, ProductCoverMetadata: productCoverMetadata, ProductName: *shoppingCart.ShoppingCartProduct.Title, ItemPrice: order.GetAmountInfo(order.GetAmountInfoReq{ ExchangeRate: in.ExchangeRate, Initiate: productPrice, Current: productPrice, CurrentCurrency: in.CurrentCurrency, OriginalCurrency: in.OriginalCurrency, }), ProductSnapshot: shoppingCart.ShoppingCartProduct, ShoppingCartSnapshot: shoppingCartSnapshotInter, ProductSn: *shoppingCart.ShoppingCartProduct.Sn, DiyInformation: &shoppingCartSnapshot.UserDiyInformation, FittingInfo: &gmodel.OrderProductFittingInfo{ FittingID: *shoppingCart.FittingId, FittingName: shoppingCartSnapshot.FittingInfo.FittingName, }, SizeInfo: &gmodel.OrderProductSizeInfo{ SizeID: *shoppingCart.SizeId, Capacity: shoppingCartSnapshot.SizeInfo.Capacity, Title: gmodel.OrderProductSizeInfoTitle{ Inch: shoppingCartSnapshot.SizeInfo.Inch, Cm: shoppingCartSnapshot.SizeInfo.Cm, }, }, IsHighlyCustomized: *shoppingCart.IsHighlyCustomized, } orderProductList = append(orderProductList, productInter) } subtotal = order.GetAmountInfo(order.GetAmountInfoReq{ ExchangeRate: in.ExchangeRate, Initiate: orderProductTotal, Current: orderProductTotal, CurrentCurrency: in.CurrentCurrency, OriginalCurrency: in.OriginalCurrency, }) orderTotal = orderProductTotal + shippingFeeTotal + taxTotal - discountTotal total = order.GetAmountInfo(order.GetAmountInfoReq{ ExchangeRate: in.ExchangeRate, Initiate: orderTotal, Current: orderTotal, CurrentCurrency: in.CurrentCurrency, OriginalCurrency: in.OriginalCurrency, }) // 定金 var depositInt int64 = orderTotal / 2 var deposit = gmodel.PayInfo{ Status: gmodel.PayStatus{ StatusCode: int64(constants.PAYSTATUSUNPAID), StatusTitle: constants.PayStatusMessage[constants.PAYSTATUSUNPAID], }, StatusLink: make([]gmodel.PayStatus, 0), PayAmount: order.GetAmountInfo(order.GetAmountInfoReq{ ExchangeRate: in.ExchangeRate, Initiate: depositInt, Current: depositInt, CurrentCurrency: in.CurrentCurrency, OriginalCurrency: in.OriginalCurrency, }), } // 尾款 var remainingBalanceInt int64 = orderTotal - depositInt var remainingBalance = gmodel.PayInfo{ Status: gmodel.PayStatus{ StatusCode: int64(constants.PAYSTATUSUNPAID), StatusTitle: constants.PayStatusMessage[constants.PAYSTATUSUNPAID], }, StatusLink: make([]gmodel.PayStatus, 0), PayAmount: order.GetAmountInfo(order.GetAmountInfoReq{ ExchangeRate: in.ExchangeRate, Initiate: remainingBalanceInt, Current: remainingBalanceInt, CurrentCurrency: in.CurrentCurrency, OriginalCurrency: in.OriginalCurrency, }), } var orderAmount = gmodel.OrderAmount{ Deposit: deposit, RemainingBalance: remainingBalance, Discount: discount, ShippingFee: shippingFee, Tax: tax, Subtotal: subtotal, Total: total, } // 订单状态--当前 var status = gmodel.OrderStatus{ Ctime: &nowTime, Utime: &nowTime, StatusCode: constants.ORDERSTATUSUNPAIDDEPOSIT, StatusTitle: constants.OrderStatusMessage[constants.ORDERSTATUSUNPAIDDEPOSIT], } // 订单状态--链路 var statusLink = order.GenerateOrderStatusLink(in.DeliveryMethod, nowTime, in.ExpectedDeliveryTime) var orderInfo = gmodel.OrderInfo{ Ctime: &nowTime, DeliveryMethod: in.DeliveryMethod, OrderSn: orderSn, Status: status, StatusLink: statusLink, } var orderDetail = gmodel.OrderDetail{ DeliveryAddress: orderAddress, OrderAmount: orderAmount, OrderInfo: orderInfo, OrderProduct: orderProductList, PayStatus: payStatus, PayTimeout: 30 * time.Minute, } // 数据库操作 orderDetailByte, err := json.Marshal(orderDetail) if err != nil { return err } var order = gmodel.FsOrder{ UserId: &in.UserId, DeliveryMethod: &in.DeliveryMethod, OrderSn: &orderSn, Status: (*int64)(&status.StatusCode), PayStatus: (*int64)(&payStatus), Ctime: &nowTime, Metadata: &orderDetailByte, } result := tx.Create(&order) if result.Error != nil { return result.Error } return nil }) if err != nil { logx.Errorf("order create order failed, err: %v", err) if errorCode.Code == 0 { errorCode.Code = basic.CodeApiErr.Code errorCode.Message = basic.CodeApiErr.Message } return &CreateRes{ OrderSn: orderSn, ErrorCode: errorCode, }, err } return &CreateRes{ OrderSn: orderSn, ErrorCode: errorCode, }, nil } // 处理订单购物车快照 func (d *defaultOrder) OrderDetailSnapshotHandler(ctx context.Context, req *string) (res map[string]interface{}, err error) { var snapshot map[string]interface{} json.Unmarshal([]byte(*req), &snapshot) snapshotFittingInfoData, snapshotFittingInfoEx := snapshot["fitting_info"] var fittingInfoMap map[string]interface{} if snapshotFittingInfoEx { var snapshotFittingInfoJson map[string]interface{} var fittingName string snapshotFittingInfo := snapshotFittingInfoData.(map[string]interface{}) snapshotFittingInfoJsonData, snapshotFittingInfoJsonEx := snapshotFittingInfo["fitting_json"] if snapshotFittingInfoJsonEx { json.Unmarshal([]byte(snapshotFittingInfoJsonData.(string)), &snapshotFittingInfoJson) } fittingNameData, fittingNameEx := snapshotFittingInfo["fitting_name"] if fittingNameEx { fittingName = fittingNameData.(string) } fittingInfoMap = make(map[string]interface{}, 2) fittingInfoMap["fitting_json"] = snapshotFittingInfoJson fittingInfoMap["fitting_name"] = fittingName } snapshot["fitting_info"] = fittingInfoMap snapshotModelInfoData, snapshotModelInfoEx := snapshot["model_info"] var modelInfoMap map[string]interface{} if snapshotModelInfoEx { var snapshotModelInfoJson map[string]interface{} snapshotModelInfo := snapshotModelInfoData.(map[string]interface{}) snapshotModelInfoJsonData, snapshotModelInfoJsonEx := snapshotModelInfo["model_json"] if snapshotModelInfoJsonEx { json.Unmarshal([]byte(snapshotModelInfoJsonData.(string)), &snapshotModelInfoJson) } modelInfoMap = make(map[string]interface{}, 1) modelInfoMap["model_json"] = snapshotModelInfoJson } snapshot["model_info"] = modelInfoMap snapshotTemplateInfoData, snapshotTemplateInfoEx := snapshot["template_info"] var templateInfoMap map[string]interface{} if snapshotTemplateInfoEx { var snapshotTemplateInfoJson map[string]interface{} var templateTag string snapshotTemplateInfo := snapshotTemplateInfoData.(map[string]interface{}) snapshotTemplateInfoJsonData, snapshotTemplateInfoJsonEx := snapshotTemplateInfo["template_json"] if snapshotTemplateInfoJsonEx { json.Unmarshal([]byte(snapshotTemplateInfoJsonData.(string)), &snapshotTemplateInfoJson) } templateTagData, templateTagEx := snapshotTemplateInfo["template_tag"] if templateTagEx { templateTag = templateTagData.(string) } templateInfoMap = make(map[string]interface{}, 2) templateInfoMap["template_json"] = snapshotTemplateInfoJson templateInfoMap["template_tag"] = templateTag } snapshot["template_info"] = templateInfoMap return snapshot, nil } // 详情处理 func (d *defaultOrder) OrderDetailHandler(ctx context.Context, orderInfo *gmodel.FsOrder, original int64) (res *DetailRes, err error) { var orderDetail gmodel.OrderDetail err = json.Unmarshal(*orderInfo.Metadata, &orderDetail) if err != nil { logx.Errorf("order detail handler unmarshal metadata failed, err: %v", err) return nil, err } orderDetailOriginal := orderDetail if original == 1 { for orderProductKey, orderProduct := range orderDetail.OrderProduct { orderDetail.OrderProduct[orderProductKey].TotalPrice = order.GetAmountInfoFormat(&orderProduct.TotalPrice) orderDetail.OrderProduct[orderProductKey].TotalPrice = order.GetAmountInfoFormat(&orderProduct.TotalPrice) orderDetail.OrderProduct[orderProductKey].PurchaseQuantity = order.GetPurchaseQuantity(&orderProduct.PurchaseQuantity) orderDetail.OrderProduct[orderProductKey].ProductSnapshot = nil orderDetail.OrderProduct[orderProductKey].ShoppingCartSnapshot.SnapshotData = nil } orderDetail.OrderInfo.StatusLink = order.GetOrderStatusLinkUser(orderDetail.OrderInfo.DeliveryMethod, orderDetail.OrderInfo.StatusLink) orderDetail.OrderAmount.Deposit.PayAmount = order.GetAmountInfoFormat(&orderDetail.OrderAmount.Deposit.PayAmount) orderDetail.OrderAmount.RemainingBalance.PayAmount = order.GetAmountInfoFormat(&orderDetail.OrderAmount.RemainingBalance.PayAmount) orderDetail.OrderAmount.Subtotal = order.GetAmountInfoFormat(&orderDetail.OrderAmount.Subtotal) orderDetail.OrderAmount.Total = order.GetAmountInfoFormat(&orderDetail.OrderAmount.Total) orderDetail.PayTimeout = time.Duration(orderDetail.OrderInfo.Ctime.Add(orderDetail.PayTimeout).UTC().Unix() - time.Now().UTC().Unix()) } return &DetailRes{ OrderDetail: orderDetail, OrderDetailOriginal: orderDetailOriginal, }, nil }