package localjx import ( "encoding/json" "fmt" "git.rosy.net.cn/baseapi/platformapi/tonglianpayapi" "git.rosy.net.cn/baseapi/utils" "git.rosy.net.cn/jx-callback/business/jxutils" "git.rosy.net.cn/jx-callback/business/jxutils/jxcontext" "git.rosy.net.cn/jx-callback/business/model" "git.rosy.net.cn/jx-callback/business/model/dao" "git.rosy.net.cn/jx-callback/business/partner" "git.rosy.net.cn/jx-callback/globals" "git.rosy.net.cn/jx-callback/globals/api" "strings" "time" ) var ( TransactionStatus = map[string]string{ "0000": "交易成功", "1001": "交易不存在", "2008": "交易处理中,请查询交易,如果是实时交易(例如刷卡支付,交易撤销,退货),建议每隔一段时间(10秒)查询交易", "2000": "交易处理中,请查询交易,如果是实时交易(例如刷卡支付,交易撤销,退货),建议每隔一段时间(10秒)查询交易", "3888": "流水号重复", "3099": "渠道商户错误", "3014": "交易金额小于应收手续费", "3031": "校验实名信息失败", "3088": "交易未支付", "3089": "撤销异常,如已影响资金24小时内会做差错退款处理", "3050": "交易已被撤销", "3999": "1", "3889": "1", "3045": "1", } ) // BarCodeScannerPay 面对面扫码支付(扫码枪) func BarCodeScannerPay(vendorOrderId string, userPaymentLabel string) (payCode string, err error) { db := dao.GetDB() goodsOrder, err := dao.GetSimpleOrder(db, vendorOrderId) if err != nil { return "", err } if goodsOrder.VendorID != model.VendorIDJX { return "", fmt.Errorf("此订单不是京西订单,无法使用该功能") } storeDetail, err := dao.GetStoreDetail(db, goodsOrder.JxStoreID, 0, "") if err != nil { return "", err } param := &tonglianpayapi.OnlinePayParam{ Trxamt: utils.Int64ToStr(goodsOrder.ActualPayPrice), Reqsn: goodsOrder.VendorOrderID, Body: storeDetail.Name, Authcode: userPaymentLabel, // 支付授权码 Chnlstoreid: utils.Int2Str(goodsOrder.JxStoreID), NotifyUrl: tonglianpayapi.OnlinePayCallbackUrl, Signtype: tonglianpayapi.EncryptionRsa, } terminfo := &tonglianpayapi.TerminfoBase{ Termno: fmt.Sprintf("%d00", goodsOrder.JxStoreID), Devicetype: "11", Termsn: fmt.Sprintf("%d00", goodsOrder.JxStoreID), Longitude: fmt.Sprintf("+%s", utils.Float64ToStr(jxutils.IntCoordinate2Standard(storeDetail.Lng))), Latitude: fmt.Sprintf("+%s", utils.Float64ToStr(jxutils.IntCoordinate2Standard(storeDetail.Lat))), } terminfoBByte, _ := json.Marshal(terminfo) param.Terminfo = string(terminfoBByte) vendorCode, trxId, remake, err := api.TLpayAPI.CreateOnlineBankOrder(param) if err != nil { return "", err } if vendorCode == tonglianpayapi.TrxStatusSuccess { goodsOrder.Status = model.OrderStatusFinished } else if vendorCode == tonglianpayapi.TrxStatusTransaction || vendorCode == tonglianpayapi.TrxStatusTransaction2 { goodsOrder.Status = model.OrderStatusWaitPay } else { goodsOrder.Status = model.OrderStatusPayFail } goodsOrder.VendorStatus = vendorCode goodsOrder.VendorOrderID2 = trxId dao.UpdateEntity(db, goodsOrder, "Status", "VendorStatus", "VendorOrderID2") partner.CurOrderManager.OnOrderMsg(goodsOrder, vendorCode, fmt.Sprintf(vendorCode+":"+TransactionStatus[vendorCode]+":%s", remake)) if vendorCode == "3045" || vendorCode == "3999" || vendorCode == "3889" { return remake, nil } return TransactionStatus[vendorCode], nil } // OnTLOnlinePayCallback 通联扫码枪扫码支付回调 func OnTLOnlinePayCallback(call *tonglianpayapi.CallBackResultOnlinePay) (err error) { switch call.TrxCode { case tonglianpayapi.MsgTypePay, tonglianpayapi.MsgTypePayZFB: err = onTLOnlinePayFinished(call) case tonglianpayapi.MsgTypeRefund, tonglianpayapi.MsgTypeRefundZFB: err = onTLOnlinePayRefund(call) default: } return err } func onTLOnlinePayFinished(call *tonglianpayapi.CallBackResultOnlinePay) (err error) { db := dao.GetDB() if order, err := partner.CurOrderManager.LoadOrder(call.CusorderID, model.VendorIDJX); err == nil { if call.TrxStatus == tonglianpayapi.TrxStatusSuccess { loc, _ := time.LoadLocation("Local") t1, _ := time.ParseInLocation("20060102150405", call.PayTime, loc) order.OrderFinishedAt = t1 order.Status = model.OrderStatusFinished } else if call.TrxStatus == tonglianpayapi.TrxStatusTransaction || call.TrxStatus == tonglianpayapi.TrxStatusTransaction2 { order.Status = model.OrderStatusWaitPay } else { order.Status = model.OrderStatusPayFail } order.VendorStatus = call.TrxStatus dao.UpdateEntity(db, order, "Status", "OrderFinishedAt", "VendorStatus") partner.CurOrderManager.OnOrderMsg(order, call.TrxStatus, fmt.Sprintf(call.TrxStatus+":"+TransactionStatus[call.TrxStatus]+":支付结果回调成功")) //if call.TrxStatus == tonglianpayapi.TrxStatusSuccess { // err = OnPayFinished(orderPay) //} } else { globals.SugarLogger.Debugf("onTLpayFinished msg:%s, err:%v", utils.Format4Output(call, true), err) } return err } func onTLOnlinePayRefund(call *tonglianpayapi.CallBackResultOnlinePay) (err error) { db := dao.GetDB() afsOrder, err := partner.CurOrderManager.LoadAfsOrder(call.CusorderID, model.VendorIDJX) if err != nil { return err } if afsOrder == nil { return fmt.Errorf("CusorderID 错误") } goodsOrder, err := partner.CurOrderManager.LoadOrder(afsOrder.VendorOrderID, model.VendorIDJX) if err != nil { return err } //partner.CurOrderManager.OnOrderMsg(goodsOrder, call.TrxStatus, fmt.Sprintf(call.TrxStatus+":"+TransactionStatus[call.TrxStatus]+":支付结果回调成功")) // 退款成功 if call.TrxStatus == tonglianpayapi.TrxStatusSuccess { afsOrder.VendorStatus = call.TrxStatus afsOrder.Status = model.AfsOrderStatusFinished afsOrder.AfsFinishedAt = time.Now() dao.UpdateEntity(db, afsOrder, "VendorStatus", "Status", "AfsFinishedAt") } // 是否全退取消订单 orderSkuCount := 0 for _, v := range goodsOrder.Skus { orderSkuCount += v.Count } afsSkus, err := dao.GetStoreAfsOrderSkuList2(db, []string{goodsOrder.VendorOrderID}) if err != nil { return err } refundSkuCount := 0 for _, v := range afsSkus { refundSkuCount += v.Count } if orderSkuCount == refundSkuCount { goodsOrder.Status = model.OrderStatusCanceled goodsOrder.VendorStatus = call.TrxStatus dao.UpdateEntity(db, goodsOrder, "Status", "VendorStatus") } return err } // QueryBarCodeScannerStatus 刷新当前订单的支付状态 func QueryBarCodeScannerStatus(vendorOrderId string) (vendorStatus string, err error) { db := dao.GetDB() goodsOrder, err := dao.GetSimpleOrder(db, vendorOrderId) if err != nil { return "", err } if goodsOrder.VendorID != model.VendorIDJX { return "", fmt.Errorf("此订单不是京西订单,无法使用该功能") } param := &tonglianpayapi.PayTransactionStatusQuery{ Reqsn: goodsOrder.VendorOrderID, Trxid: "", Randomstr: utils.GetUUID(), Signtype: tonglianpayapi.EncryptionRsa, Sign: "", } vendorCode, remake, err := api.TLpayAPI.QueryPayTransaction(param) if err != nil { return "", err } if vendorCode == tonglianpayapi.TrxStatusSuccess { goodsOrder.OrderFinishedAt = time.Now() goodsOrder.Status = model.OrderStatusFinished } else if vendorCode == tonglianpayapi.TrxStatusTransaction || vendorCode == tonglianpayapi.TrxStatusTransaction2 { goodsOrder.Status = model.OrderStatusWaitPay } else { goodsOrder.Status = model.OrderStatusPayFail } goodsOrder.VendorStatus = vendorCode dao.UpdateEntity(db, goodsOrder, "Status", "VendorStatus", "OrderFinishedAt") partner.CurOrderManager.OnOrderMsg(goodsOrder, vendorCode, fmt.Sprintf("扫码枪扫码刷新结果:[code:%s,说明:%s,异常原因:%s],本地状态[%d]", vendorCode, TransactionStatus[vendorCode], remake, goodsOrder.Status)) if vendorCode == "3045" || vendorCode == "3999" || vendorCode == "3889" { return remake, nil } return TransactionStatus[vendorCode], nil } // RefundBarCodeScannerOrder 扫码支付订单退单 func RefundBarCodeScannerOrder(ctx *jxcontext.Context, vendorOrderId string, skuIds map[int]int, reason string) (err error) { db := dao.GetDB() txDB, _ := dao.Begin(db) defer func() { if r := recover(); r != nil || err != nil { dao.Rollback(db, txDB) if r != nil { panic(r) } } }() goodsOrder, err := partner.CurOrderManager.LoadOrder(vendorOrderId, model.VendorIDJX) if err != nil { dao.Rollback(db, txDB) return err } // 检查订单售后 refundType, refundMoney, err := checkJxAfsOrder(db, goodsOrder, vendorOrderId, skuIds) if err != nil { dao.Rollback(db, txDB) return err } // 本地添加售后数据 afsOrder, orderSkuFinancial, afsOrderSkuFinancial, err := addRefundSku(ctx, goodsOrder, skuIds, reason, refundType) if err != nil { dao.Rollback(db, txDB) return err } // 通知退款,等待回传 status, errMsg, err := api.TLpayAPI.OrderRefund(&tonglianpayapi.OnLineOrderRefundParam{ Trxamt: utils.Int2Str(refundMoney), Reqsn: afsOrder.AfsOrderID, Oldreqsn: goodsOrder.VendorOrderID, Oldtrxid: "", Remark: reason, Benefitdetail: "", Randomstr: utils.GetUUID(), Signtype: tonglianpayapi.EncryptionRsa, Sign: "", }) if err != nil { return err } switch status { case "0000", "1001", "2008", "2000": if err = dao.CreateEntity(db, afsOrder); err != nil { dao.Rollback(db, txDB) return err } if err = dao.CreateMultiEntities(db, orderSkuFinancial); err != nil { dao.Rollback(db, txDB) return err } if err = dao.CreateMultiEntities(db, afsOrderSkuFinancial); err != nil { dao.Rollback(db, txDB) return err } goodsOrder.TotalShopMoney -= int64(refundMoney) dao.UpdateEntity(db, goodsOrder, "TotalShopMoney") dao.Commit(db, txDB) default: return fmt.Errorf(errMsg) } return nil } // AddStoreTerm 添加设备终端 func AddStoreTerm(storeId int, operation string) error { storeDetail, err := dao.GetStoreDetail(dao.GetDB(), storeId, model.VendorIDJX, "") if err != nil { return err } index := strings.LastIndex(storeDetail.Address, storeDetail.DistrictName) address := "" if index < 0 { address = storeDetail.Address } else { address = storeDetail.Address[index+len(storeDetail.DistrictName):] } param := &tonglianpayapi.AddTermReq{ Termno: fmt.Sprintf("%d00", storeDetail.ID), Devicetype: "11", Termsn: fmt.Sprintf("%d00", storeDetail.ID), Operation: operation, Termstate: "", Termaddress: fmt.Sprintf("%s-%s-%s-%s", storeDetail.ProvinceName, storeDetail.CityName, storeDetail.DistrictName, address), Signtype: tonglianpayapi.EncryptionRsa, Sign: "", } if operation == tonglianpayapi.ScannerOperrationStart { param.Termstate = tonglianpayapi.ScannerOperrationStart } return api.TLpayAPI.TLAddTerm(param) } // QueryStoreTerm 查询设备终端状态 func QueryStoreTerm(storeId int) (string, error) { storeDetail, err := dao.GetStoreDetail(dao.GetDB(), storeId, model.VendorIDJX, "") if err != nil { return "", err } param := &tonglianpayapi.AddTermQuery{ Termno: fmt.Sprintf("%d00", storeDetail.ID), Signtype: tonglianpayapi.EncryptionRsa, Sign: "", } return api.TLpayAPI.TLQueryTerm(param) } func checkJxAfsOrder(db *dao.DaoDB, goodsOrder *model.GoodsOrder, vendorOrderId string, skuIds map[int]int) (bool, int, error) { if goodsOrder.Status != model.OrderStatusFinished { return false, 0, fmt.Errorf("订单未完成支付,无法退款(刷新订单状态/联系管理员)") } // 未结束的售后单,不让继续添加售后单了 completeAfsOrder, _ := dao.GetAfsOrders(db, model.VendorIDJX, vendorOrderId, "") if len(completeAfsOrder) != model.NO { for _, v := range completeAfsOrder { if v.Status != model.AfsOrderStatusFinished { return false, 0, fmt.Errorf("此订单还有未完成的售后单,请稍后再处理") } } } totalCount := 0 // 总的商品个数 skuList := make(map[int]*model.OrderSku, 0) for _, v := range goodsOrder.Skus { skuList[v.SkuID] = v totalCount += v.Count } // 已经完成商品个数+当次录入商品个数>订单商品个数 不让继续退了 afsSkuList, _ := dao.GetStoreAfsOrderSkuList2(db, []string{vendorOrderId}) // 已经退款商品 afsCount := 0 // 总的售后个数 for _, v := range afsSkuList { if v.Count+skuIds[v.SkuID] > skuList[v.SkuID].Count { return false, 0, fmt.Errorf("商品[%s],记录购买个数为:%d个,退款个数为:%d个,请检查输入", skuList[v.SkuID].SkuName, skuList[v.SkuID].Count, v.Count+skuIds[v.SkuID]) } afsCount += v.Count + skuIds[v.SkuID] } if afsCount > totalCount { return false, 0, fmt.Errorf("退货商品数计算结果大于订单商品数,请联系管理员") } totalRefundMoney := 0 // 总退款金额 for skuId, skuCount := range skuIds { if skuList[skuId] != nil && skuList[skuId].Count >= skuCount { totalRefundMoney += int(skuList[skuId].SalePrice) * skuCount } } return totalCount == afsCount, totalRefundMoney, nil } func addRefundSku(ctx *jxcontext.Context, goodsOrder *model.GoodsOrder, skuIds map[int]int, reason string, refundType bool) (afsOrder *model.AfsOrder, skuFinancial, afsSkuFinancial []*model.OrderSkuFinancial, err error) { refundMoney := 0 // 退款金额 afsOrderID := utils.Int64ToStr(jxutils.GenAfsOrderNo()) // 退款Id orderSkuFinancial := make([]*model.OrderSkuFinancial, 0, len(goodsOrder.Skus)) // 所有商品 afsOrderSkuFinancial := make([]*model.OrderSkuFinancial, 0, len(skuIds)) // 申请退款商品 for _, v := range goodsOrder.Skus { orderSku := &model.OrderSkuFinancial{ ModelIDCUL: model.ModelIDCUL{ CreatedAt: time.Now(), UpdatedAt: time.Now(), LastOperator: ctx.GetUserName(), }, VendorID: model.VendorIDJX, VendorOrderID: goodsOrder.VendorOrderID, VendorSubOrderID: "", AfsOrderID: "", IsAfsOrder: 0, VendorStoreID: utils.Int2Str(goodsOrder.JxStoreID), StoreID: goodsOrder.JxStoreID, JxStoreID: goodsOrder.JxStoreID, VendorSkuID: v.VendorSkuID, SkuID: v.SkuID, JxSkuID: v.JxSkuID, PromotionType: 0, Name: v.SkuName, ShopPrice: v.ShopPrice, SalePrice: v.SalePrice, Count: v.Count, SkuBoxMoney: 0, UserMoney: v.SalePrice, PmSubsidyMoney: 0, PmSkuSubsidyMoney: 0, PmDeductionsMoney: 0, ShopMoney: v.SalePrice, ShopMoneyByCal: 0, JxSubsidyMoney: 0, JxSkuSubsidyMoney: 0, JxDeductionsMoney: 0, JxShopMoney: v.SalePrice, RefundMoney: 0, RefundMoneyByCal: 0, StoreSubID: 0, StoreSubName: "", } orderSkuFinancial = append(orderSkuFinancial, orderSku) if skuIds[v.SkuID] != 0 && v.Count >= skuIds[v.SkuID] { refundMoney += int(v.SalePrice) * v.Count orderSku.AfsOrderID = afsOrderID orderSku.IsAfsOrder = 1 orderSku.Count = skuIds[v.SkuID] orderSku.RefundMoney = v.SalePrice * int64(v.Count) afsOrderSkuFinancial = append(afsOrderSkuFinancial, orderSku) } } afsOrder = &model.AfsOrder{ ModelIDCUL: model.ModelIDCUL{ CreatedAt: time.Now(), UpdatedAt: time.Now(), LastOperator: ctx.GetUserName(), }, VendorID: model.VendorIDJX, AfsOrderID: afsOrderID, VendorOrderID: goodsOrder.VendorOrderID, VendorStoreID: utils.Int2Str(goodsOrder.StoreID), StoreID: goodsOrder.StoreID, AfsCreatedAt: time.Now(), VendorAppealType: utils.Int2Str(model.VendorIDJX), AppealType: model.AfsAppealTypeReturnAndRefund, VendorReasonType: "", ReasonType: model.AfsReasonNotOthers, ReasonDesc: utils.LimitUTF8StringLen(reason, 1024), ReasonImgList: "", RefundType: model.AfsTypePartRefund, VendorOrgCode: "0", RefundMoney: int64(refundMoney), Status: model.AfsOrderStatusWait4Approve, Flag: 0, AfsTotalShopMoney: goodsOrder.ActualPayPrice - int64(refundMoney), } if refundType { afsOrder.RefundType = model.AfsTypeFullRefund } return afsOrder, orderSkuFinancial, afsOrderSkuFinancial, err }