Files
jx-callback/business/partner/purchase/jx/localjx/tonglianpay_gun.go
邹宗楠 e8e3eb7c09 1
2025-08-06 09:32:25 +08:00

471 lines
16 KiB
Go

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
}