1、商城订单详情、列表增加"退款时间"的字段;
2、商城订单退货时如果时明慧帐户则走河马付退款流程; 3、新增河马付退款接口;
This commit is contained in:
parent
bbbc61f857
commit
452a7d54d9
|
@ -82,12 +82,17 @@ const (
|
|||
HmPayApiUrl = "https://hmpay.sandpay.com.cn/gateway/api"
|
||||
PemBegin = "-----BEGIN RSA PRIVATE KEY-----\n"
|
||||
PemEnd = "\n-----END RSA PRIVATE KEY-----"
|
||||
HmPubKey = `MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDzGVH0Fxpb2M48U1BWr6lpNs2W3VHqtjO8X5RqWjtTwpQVKo8dqaiAGxVbsdnefPpsbI5l9rKquRAOJhWFU07hxSUgXZOk55QQmll03MBgRDXLgxyKfycLLQwhsCJAzDIWC7IWgok/RHV9m9AV2GbQxWBl+7iDE4prcbpgG8Z0HwIDAQAB`
|
||||
HmPayMerchantId = "664403000030115"
|
||||
|
||||
TimeFormat = "2006-01-02 15:04:05"
|
||||
clientIp = "39.108.188.218" // 小程序服务器
|
||||
clientIpDev = "112.33.14.191" // 移动云服务器
|
||||
|
||||
// HmPayMerchantId 明慧帐户
|
||||
HmPayMerchantId = "664403000030115"
|
||||
HmPubKey = `MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDzGVH0Fxpb2M48U1BWr6lpNs2W3VHqtjO8X5RqWjtTwpQVKo8dqaiAGxVbsdnefPpsbI5l9rKquRAOJhWFU07hxSUgXZOk55QQmll03MBgRDXLgxyKfycLLQwhsCJAzDIWC7IWgok/RHV9m9AV2GbQxWBl+7iDE4prcbpgG8Z0HwIDAQAB`
|
||||
HmPubKeyFp = "./config/hm_pay/private_key.pem"
|
||||
TestHmPubKeyFp = "/Users/max/Documents/code/deovo/mh_goadmin_server/config/hm_pay/private_key.pem"
|
||||
|
||||
// HmPayMerchantIdSwitch 任天堂项目-对私账户(密钥20240701203620)
|
||||
HmPayMerchantIdSwitch = "664403000021193"
|
||||
HmPubKeySwitch = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCDA4g8VFWIxEbOzxYC8ZIOgaOsLWK4Y5k9D8GwJ1Gige79LbTxbe3PH12KMc59DpCR1PnIDwlYWjIE7mZZAHgImXs0pSFihvlNS9srWk2uPlEXXQjjIZ3mnPoXtNhU0x5cYdkB8jtijcYMSGwKmdrIvpvPX3MrDKOX6dJ1T4ll+QIDAQAB"
|
||||
|
@ -520,8 +525,15 @@ func TransactionOrderRefund(orderRefund OrderRefund, accountNum uint32) error {
|
|||
|
||||
if err != nil {
|
||||
// 处理错误
|
||||
log.Printf("call Create err:%s", err)
|
||||
var apiErr *core.APIError
|
||||
if errors.As(err, &apiErr) {
|
||||
log.Printf("call Create err: %s", apiErr.Message)
|
||||
return fmt.Errorf(apiErr.Message)
|
||||
} else {
|
||||
// 不是 APIError 类型,打印默认错误
|
||||
log.Printf("call Create err: %s", err.Error())
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// 处理返回结果
|
||||
log.Printf("status=%d resp=%s", result.Response.StatusCode, resp)
|
||||
|
@ -696,6 +708,42 @@ type HmPayTradeCancelResp struct {
|
|||
ReqReserved string `json:"req_reserved"` // 商户自定义字段
|
||||
}
|
||||
|
||||
// HmPayTradeRefundContent 订单退款 trade.refund 业务请求参数
|
||||
type HmPayTradeRefundContent struct {
|
||||
OrderCreateTime string `json:"order_create_time,omitempty"` // 订单创建时间,格式:yyyyMMddHHmmss(可选,但与 out_order_no 联动必填)
|
||||
OutOrderNo string `json:"out_order_no,omitempty"` // 商户订单号,三选一必填之一
|
||||
PlatTrxNo string `json:"plat_trx_no,omitempty"` // 平台交易流水号,三选一必填之一
|
||||
BankOrderNo string `json:"bank_order_no,omitempty"` // 银行订单号,三选一必填之一
|
||||
RefundAmount float64 `json:"refund_amount"` // 退款金额,单位元
|
||||
RefundRequestNo string `json:"refund_request_no"` // 商户退款请求号,同一订单退款不可重复
|
||||
}
|
||||
|
||||
// HmPayTradeRefundResp 订单退款 trade.refund 业务响应参数
|
||||
type HmPayTradeRefundResp struct {
|
||||
SubCode string `json:"sub_code"` // 业务返回码
|
||||
SubMsg string `json:"sub_msg"` // 业务返回信息
|
||||
RefundAmount float64 `json:"refund_amount"` // 退款金额
|
||||
OutOrderNo string `json:"out_order_no"` // 商户订单号
|
||||
PlatTrxNo string `json:"plat_trx_no"` // 平台交易流水号
|
||||
BankOrderNo string `json:"bank_order_no"` // 银行订单号
|
||||
BankTrxNo string `json:"bank_trx_no"` // 银行流水号
|
||||
PayWayCode string `json:"pay_way_code"` // 支付方式
|
||||
BuyerID string `json:"buyer_id"` // 买家ID
|
||||
RefundRequestNo string `json:"refund_request_no"` // 商户退款请求号
|
||||
RefundPlatTrxNo string `json:"refund_plat_trx_no"` // 退款平台流水号
|
||||
RefundBankOrderNo string `json:"refund_bank_order_no"` // 退款银行订单号
|
||||
RefundBankTrxNo string `json:"refund_bank_trx_no,omitempty"` // 退款银行流水号(可选)
|
||||
ReqReserved string `json:"req_reserved,omitempty"` // 自定义字段(可选)
|
||||
DiscountDetail *DiscountDetail `json:"discount_detail,omitempty"` // 优惠详情(可选)
|
||||
}
|
||||
|
||||
// DiscountDetail 示例优惠详情结构体(根据业务自行扩展)
|
||||
type DiscountDetail struct {
|
||||
// 可根据具体业务定义字段结构
|
||||
DiscountType string `json:"discount_type,omitempty"` // 示例:优惠类型
|
||||
DiscountAmount float64 `json:"discount_amount,omitempty"` // 示例:优惠金额
|
||||
}
|
||||
|
||||
type HmPayUnifiedOrderPayData struct {
|
||||
TimeStamp string `json:"timeStamp"`
|
||||
Package string `json:"package"`
|
||||
|
@ -1086,6 +1134,12 @@ func getStoreKeyConfig(storeId uint32) StoreKeyConfig {
|
|||
PubKey: HmPubKeyJBL,
|
||||
}
|
||||
}
|
||||
case 100001: // 明慧帐户-小程序商城
|
||||
config = StoreKeyConfig{
|
||||
AppID: HmPayMerchantId,
|
||||
FP: HmPubKeyFp,
|
||||
PubKey: HmPubKey,
|
||||
}
|
||||
}
|
||||
|
||||
return config
|
||||
|
@ -1264,9 +1318,10 @@ func HmCancelOrder(orderId string, storeId uint32) (*HmPayTradeCancelResp, error
|
|||
now := time.Now().Local()
|
||||
nonce := GenRandStr(NonceStringLength)
|
||||
|
||||
storeKey := getStoreKeyConfig(storeId)
|
||||
unifiedOrderReq := HmJsPayUnifiedOrderReq{}
|
||||
publicPara := HmPayPublicPara{
|
||||
AppId: HmPayMerchantIdSwitch,
|
||||
AppId: storeKey.AppID,
|
||||
Method: "trade.cancel",
|
||||
SignType: "RSA",
|
||||
Sign: "",
|
||||
|
@ -1326,3 +1381,75 @@ func HmCancelOrder(orderId string, storeId uint32) (*HmPayTradeCancelResp, error
|
|||
|
||||
return &hmPayDetail, nil
|
||||
}
|
||||
|
||||
// HmRefundOrder 订单退款
|
||||
func HmRefundOrder(req HmPayTradeRefundContent, storeId uint32) (*HmPayTradeRefundResp, error) {
|
||||
now := time.Now().Local()
|
||||
nonce := GenRandStr(NonceStringLength)
|
||||
|
||||
storeKey := getStoreKeyConfig(storeId)
|
||||
unifiedOrderReq := HmJsPayUnifiedOrderReq{}
|
||||
publicPara := HmPayPublicPara{
|
||||
AppId: storeKey.AppID,
|
||||
Method: "trade.refund",
|
||||
SignType: "RSA",
|
||||
Sign: "",
|
||||
Timestamp: now.Format(TimeFormat),
|
||||
Nonce: nonce,
|
||||
}
|
||||
|
||||
biz := HmPayTradeRefundContent{
|
||||
OutOrderNo: req.OutOrderNo,
|
||||
PlatTrxNo: req.PlatTrxNo,
|
||||
RefundAmount: req.RefundAmount,
|
||||
RefundRequestNo: req.RefundRequestNo,
|
||||
}
|
||||
unifiedOrderReq.HmPayPublicPara = publicPara
|
||||
|
||||
bizString, err := json.Marshal(&biz)
|
||||
if err != nil {
|
||||
logger.Error("marshal biz err:", logger.Field("err", err))
|
||||
return nil, err
|
||||
}
|
||||
unifiedOrderReq.HmPayPublicPara.BizContent = string(bizString)
|
||||
m, err := struct2Map(unifiedOrderReq)
|
||||
if err != nil {
|
||||
logger.Error("HmJsPayUnifiedOrder struct2Map err:", logger.Field("err", err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sign, err := GenHmPaySignDeovo(storeId, m)
|
||||
if err != nil {
|
||||
logger.Error("HmJsPayUnifiedOrder GenHmPaySign err:", logger.Field("err", err))
|
||||
return nil, err
|
||||
}
|
||||
unifiedOrderReq.Sign = sign
|
||||
|
||||
unifiedOrderResp, err := HmPayUnifiedOrder(unifiedOrderReq)
|
||||
if err != nil {
|
||||
logger.Errorf("WxUnifiedOrder unified order error %#v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signContent, err := ToSignContent(unifiedOrderResp)
|
||||
if err != nil {
|
||||
logger.Errorf("ToSignContent err:", err)
|
||||
return nil, err
|
||||
}
|
||||
err = HmVerifySha1RsaDeovo(storeId, signContent, unifiedOrderResp.Sign)
|
||||
if err != nil {
|
||||
logger.Errorf("HmVerifySha1Rsa err:", err)
|
||||
return nil, err
|
||||
}
|
||||
fmt.Println("Resp Data:", unifiedOrderResp.Data)
|
||||
|
||||
var hmPayDetail HmPayTradeRefundResp
|
||||
err = json.Unmarshal([]byte(unifiedOrderResp.Data), &hmPayDetail)
|
||||
if err != nil {
|
||||
logger.Errorf("hm pay unified order pay data unmarshal error %#v", err)
|
||||
return nil, err
|
||||
}
|
||||
fmt.Println("hmPayDetail:", hmPayDetail)
|
||||
|
||||
return &hmPayDetail, nil
|
||||
}
|
||||
|
|
|
@ -53,9 +53,26 @@ func TestHmQueryOrder(t *testing.T) {
|
|||
|
||||
// 撤销
|
||||
func TestHmCancelOrder(t *testing.T) {
|
||||
orderId := "sale2023122225067733"
|
||||
orderId := "CF56C27AE0"
|
||||
|
||||
order, err := HmCancelOrder(orderId, 13)
|
||||
order, err := HmCancelOrder(orderId, 100001)
|
||||
if err != nil {
|
||||
fmt.Println("err:", err)
|
||||
}
|
||||
|
||||
fmt.Println("order:", order)
|
||||
}
|
||||
|
||||
func TestHmRefundOrder(t *testing.T) {
|
||||
|
||||
testReq := HmPayTradeRefundContent{
|
||||
OutOrderNo: "CF56C27AE0",
|
||||
BankOrderNo: "HMP2505270003044236901990400",
|
||||
RefundAmount: 0.11,
|
||||
RefundRequestNo: "R202505291545301234567890",
|
||||
}
|
||||
|
||||
order, err := HmRefundOrder(testReq, 100001)
|
||||
if err != nil {
|
||||
fmt.Println("err:", err)
|
||||
}
|
||||
|
|
|
@ -7,12 +7,14 @@ import (
|
|||
"github.com/gin-gonic/gin"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/xuri/excelize/v2"
|
||||
"go-admin/app/admin/apis/pay"
|
||||
utils "go-admin/app/admin/models/tools"
|
||||
orm "go-admin/common/global"
|
||||
"go-admin/logger"
|
||||
"go-admin/tools"
|
||||
"go-admin/tools/config"
|
||||
"gorm.io/gorm"
|
||||
"math/rand"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
@ -340,6 +342,7 @@ type GoodsOrder struct {
|
|||
RefundExpressNo string `json:"refund_express_no"` // 退货物流单号
|
||||
RefundReason string `json:"refund_reason" gorm:"type:text;"`
|
||||
ReceivedTime time.Time `json:"received_time"` // 签收时间
|
||||
RefundTime time.Time `json:"refund_time" gorm:"-"` // 退货时间
|
||||
|
||||
VersionId uint64 `json:"version_id"` // 乐观锁
|
||||
|
||||
|
@ -635,6 +638,9 @@ func (m *GoodsOrderDetailReq) OrderDetail() (*GoodsOrder, error) {
|
|||
}
|
||||
goods.Attributes = attributes
|
||||
order.Goods = &goods
|
||||
if order.State == GoodsOrderStateRefunded { // 已退货状态
|
||||
order.RefundTime = order.UpdatedAt
|
||||
}
|
||||
|
||||
var userAddress UserAddress
|
||||
err = orm.Eloquent.Table("user_address").Where("id=?", order.AddressId).Find(&userAddress).Error
|
||||
|
@ -732,6 +738,9 @@ func GoodsOrderListSetGoods(orders []GoodsOrder) {
|
|||
ids := make([]uint32, 0, len(orders))
|
||||
for i, _ := range orders {
|
||||
ids = append(ids, orders[i].GoodsId)
|
||||
if orders[i].State == GoodsOrderStateRefunded { // 已退货状态
|
||||
orders[i].RefundTime = orders[i].UpdatedAt
|
||||
}
|
||||
}
|
||||
goodsMap := GetGoodsMapByIds(ids)
|
||||
for i, _ := range orders {
|
||||
|
@ -1369,12 +1378,63 @@ func (r *GoodsOrderRefundSendReceiveReq) Receive() error {
|
|||
}
|
||||
|
||||
if goodsOrder.Rm != 0 { // 如果有支付,则走退款流程
|
||||
if goods.GoodsAccountNum == 1 { // 明慧,默认使用杉德河马付
|
||||
// 记录UserOpenMemberRecord
|
||||
memberRecord.Uid = goodsOrder.Uid
|
||||
memberRecord.OrderId = goodsOrder.ID
|
||||
err = orm.Eloquent.Create(memberRecord).Error
|
||||
if err != nil {
|
||||
logger.Error("insert user open member record err:", logger.Field("err", err))
|
||||
return err
|
||||
}
|
||||
|
||||
// 查询付款信息
|
||||
var fundRecord FundRecord
|
||||
err = orm.Eloquent.Table("fund_record").Where("out_trade_no=?", outTradeNo).Find(&fundRecord).Error
|
||||
if err != nil || fundRecord.ID == 0 {
|
||||
log.Error().Msgf("get fund_record err:%#v", err)
|
||||
return errors.New("未查询到付款信息")
|
||||
}
|
||||
|
||||
refundReq := pay.HmPayTradeRefundContent{
|
||||
OutOrderNo: outTradeNo,
|
||||
BankOrderNo: fundRecord.TransactionId,
|
||||
RefundAmount: float64(fundRecord.Amount) / 100.0,
|
||||
RefundRequestNo: GenerateRefundOrderNo(),
|
||||
}
|
||||
resp, err := pay.HmRefundOrder(refundReq, 100001)
|
||||
if err != nil {
|
||||
log.Error().Msgf("order refund err:%#v", err)
|
||||
return err
|
||||
}
|
||||
if resp.SubCode != "REFUND_SUCCESS" {
|
||||
log.Error().Msg("河马付退款失败")
|
||||
return errors.New(resp.SubMsg)
|
||||
} else { // 退款成功,记录订单信息
|
||||
refundRecord := &FundRecord{
|
||||
Uid: fundRecord.Uid,
|
||||
FundType: "buy_goods_refund",
|
||||
Amount: int64(resp.RefundAmount*100) * (-1),
|
||||
TransactionId: resp.BankOrderNo,
|
||||
OutTradeNo: resp.OutOrderNo,
|
||||
RefundId: resp.RefundBankTrxNo,
|
||||
Status: 2,
|
||||
Remark: "商城购买退货",
|
||||
}
|
||||
err = orm.Eloquent.Create(refundRecord).Error
|
||||
if err != nil {
|
||||
logger.Errorf("create fund record err:", err.Error())
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
err = memberRecord.MallGoodsOrderRefund(outTradeNo, goods.GoodsAccountNum)
|
||||
if err != nil {
|
||||
log.Error().Msgf("order refund err:%#v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 更新订单为"已退货"状态
|
||||
err = begin.Table("goods_order").Where("order_id=?", r.OrderId).Update("state", GoodsOrderStateRefunded).Error
|
||||
|
@ -1426,6 +1486,23 @@ func (r *GoodsOrderRefundSendReceiveReq) Receive() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// GenerateRefundOrderNo 生成退款订单号(例如:R202505271545301234567890)
|
||||
func GenerateRefundOrderNo() string {
|
||||
// 设置随机种子
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
// 当前时间:年月日时分秒(14位)
|
||||
timestamp := time.Now().Format("20060102150405")
|
||||
|
||||
// 生成 10 位随机数:范围是 1000000000 ~ 9999999999
|
||||
randomNum := rand.Int63n(9000000000) + 1000000000 // int63 支持更大范围
|
||||
|
||||
// 拼接最终退款订单号
|
||||
refundOrderNo := fmt.Sprintf("R%s%d", timestamp, randomNum)
|
||||
|
||||
return refundOrderNo
|
||||
}
|
||||
|
||||
func GetWxPayExpressFeeRefundRecord(orderId uint32) (string, error) {
|
||||
var openMemberRecord UserOpenMemberRecord
|
||||
err := orm.Eloquent.Table("user_open_member_record").Where("order_id=?", orderId).
|
||||
|
@ -1441,11 +1518,19 @@ func OrderUpdateGoodsStockBack(attributeId, count uint32, gdb *gorm.DB) error {
|
|||
if gdb == nil {
|
||||
gdb = orm.Eloquent
|
||||
}
|
||||
sql := fmt.Sprintf("UPDATE goods_attribute SET stock=stock+%d,sold_count=sold_count-%d WHERE id=%d ",
|
||||
count, count, attributeId)
|
||||
err := gdb.Exec(sql).Error
|
||||
|
||||
// 防止 sold_count 小于 count,避免变成负数
|
||||
sql := `
|
||||
UPDATE goods_attribute
|
||||
SET stock = stock + ?,
|
||||
sold_count = sold_count - ?
|
||||
WHERE id = ?
|
||||
AND sold_count >= ?
|
||||
`
|
||||
|
||||
err := gdb.Exec(sql, count, count, attributeId, count).Error
|
||||
if err != nil {
|
||||
logger.Errorf("err:", err)
|
||||
logger.Errorf("OrderUpdateGoodsStockBack error: %v", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
|
Loading…
Reference in New Issue
Block a user