优化订单支付
This commit is contained in:
@@ -180,6 +180,8 @@ func (r *Repository) Start(userID uint64, orderID uint64, req StartPaymentReques
|
||||
return nil, ErrPaymentUnavailable
|
||||
}
|
||||
|
||||
log.Printf("[payment] payment start order_id=%d order_no=%s payment_id=%d provider=%s amount_cent=%d third_order_id=%s",
|
||||
orderID, orderRow.OrderNo, payment.ID, runtimeConfig.Provider, payment.AmountCent, payment.ThirdOrderID)
|
||||
resp, err := runtimeConfig.Channel.CreatePayment(context.Background(), channelCreatePaymentRequest{
|
||||
ThirdOrderID: payment.ThirdOrderID,
|
||||
AmountCent: payment.AmountCent,
|
||||
@@ -193,10 +195,14 @@ func (r *Repository) Start(userID uint64, orderID uint64, req StartPaymentReques
|
||||
})
|
||||
if err != nil {
|
||||
_ = r.markPaymentFailed(payment.ID, nil, err.Error())
|
||||
log.Printf("[payment] payment request failed order_id=%d payment_id=%d provider=%s amount_cent=%d err=%v",
|
||||
orderID, payment.ID, runtimeConfig.Provider, payment.AmountCent, err)
|
||||
return nil, err
|
||||
}
|
||||
if !resp.OK {
|
||||
_ = r.markPaymentFailed(payment.ID, resp.Raw, resp.ErrorMessage)
|
||||
log.Printf("[payment] payment rejected order_id=%d payment_id=%d provider=%s amount_cent=%d code=%s message=%s",
|
||||
orderID, payment.ID, runtimeConfig.Provider, payment.AmountCent, firstNonEmpty(resp.Raw["code"], resp.Raw["resp_code"], resp.Raw["result_code"]), resp.ErrorMessage)
|
||||
return nil, ErrPaymentUnavailable
|
||||
}
|
||||
if err := r.db.Model(&model.PaymentOrder{}).Where("id = ?", payment.ID).Updates(map[string]any{
|
||||
@@ -216,6 +222,8 @@ func (r *Repository) Start(userID uint64, orderID uint64, req StartPaymentReques
|
||||
return nil, err
|
||||
}
|
||||
r.recordConfigUsage(runtimeConfig, latest)
|
||||
log.Printf("[payment] payment result order_id=%d order_no=%s payment_id=%d provider=%s amount_cent=%d status=%s provider_order_id=%s",
|
||||
orderID, orderRow.OrderNo, latest.ID, runtimeConfig.Provider, latest.AmountCent, latest.Status, latest.ProviderOrderID)
|
||||
dto := toDTO(*latest)
|
||||
return &dto, nil
|
||||
}
|
||||
@@ -254,6 +262,8 @@ func (r *Repository) StartWalletRecharge(userID uint64, req WalletRechargePaymen
|
||||
_ = r.markPaymentFailed(payment.ID, nil, "payment channel unavailable")
|
||||
return nil, ErrPaymentUnavailable
|
||||
}
|
||||
log.Printf("[payment] wallet recharge start user_id=%d payment_id=%d provider=%s amount_cent=%d third_order_id=%s",
|
||||
userID, payment.ID, runtimeConfig.Provider, payment.AmountCent, payment.ThirdOrderID)
|
||||
resp, err := runtimeConfig.Channel.CreatePayment(context.Background(), channelCreatePaymentRequest{
|
||||
ThirdOrderID: payment.ThirdOrderID,
|
||||
AmountCent: payment.AmountCent,
|
||||
@@ -267,10 +277,14 @@ func (r *Repository) StartWalletRecharge(userID uint64, req WalletRechargePaymen
|
||||
})
|
||||
if err != nil {
|
||||
_ = r.markPaymentFailed(payment.ID, nil, err.Error())
|
||||
log.Printf("[payment] wallet recharge request failed user_id=%d payment_id=%d provider=%s amount_cent=%d err=%v",
|
||||
userID, payment.ID, runtimeConfig.Provider, payment.AmountCent, err)
|
||||
return nil, err
|
||||
}
|
||||
if !resp.OK {
|
||||
_ = r.markPaymentFailed(payment.ID, resp.Raw, resp.ErrorMessage)
|
||||
log.Printf("[payment] wallet recharge rejected user_id=%d payment_id=%d provider=%s amount_cent=%d code=%s message=%s",
|
||||
userID, payment.ID, runtimeConfig.Provider, payment.AmountCent, firstNonEmpty(resp.Raw["code"], resp.Raw["resp_code"], resp.Raw["result_code"]), resp.ErrorMessage)
|
||||
return nil, ErrPaymentUnavailable
|
||||
}
|
||||
if err := r.db.Model(&model.PaymentOrder{}).Where("id = ?", payment.ID).Updates(map[string]any{
|
||||
@@ -290,6 +304,8 @@ func (r *Repository) StartWalletRecharge(userID uint64, req WalletRechargePaymen
|
||||
return nil, err
|
||||
}
|
||||
r.recordConfigUsage(runtimeConfig, latest)
|
||||
log.Printf("[payment] wallet recharge result user_id=%d payment_id=%d provider=%s amount_cent=%d status=%s provider_order_id=%s",
|
||||
userID, latest.ID, runtimeConfig.Provider, latest.AmountCent, latest.Status, latest.ProviderOrderID)
|
||||
dto := toDTO(*latest)
|
||||
return &dto, nil
|
||||
}
|
||||
@@ -466,6 +482,8 @@ func (r *Repository) StartRefund(orderID uint64, refundAmountCent int64, bizType
|
||||
if err := r.db.Create(&refundOrder).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Printf("[payment] refund start order_id=%d order_no=%s payment_id=%d biz_type=%s provider=%s amount_cent=%d merchant_refund_id=%s origin_third_order_id=%s origin_provider_order_id=%s",
|
||||
orderID, originalPayment.OrderNo, refundOrder.ID, bizType, runtimeConfig.Provider, refundAmountCent, merchantRefundID, originalPayment.ThirdOrderID, refundOriginProviderOrderID(originalPayment))
|
||||
r.recordConfigUsage(runtimeConfig, &refundOrder)
|
||||
if err := r.markOrderRefunding(orderID, refundAmountCent); err != nil {
|
||||
log.Printf("[payment] mark order refunding failed order_id=%d err=%v", orderID, err)
|
||||
@@ -477,7 +495,7 @@ func (r *Repository) StartRefund(orderID uint64, refundAmountCent int64, bizType
|
||||
}
|
||||
resp, err := runtimeConfig.Channel.CreateRefund(context.Background(), channelCreateRefundRequest{
|
||||
ThirdOrderID: originalPayment.ThirdOrderID,
|
||||
ProviderOrderID: originalPayment.ProviderOrderID,
|
||||
ProviderOrderID: refundOriginProviderOrderID(originalPayment),
|
||||
MerchantRefundID: merchantRefundID,
|
||||
RefundAmountCent: refundAmountCent,
|
||||
NotifyURL: runtimeConfig.NotifyURL,
|
||||
@@ -486,10 +504,14 @@ func (r *Repository) StartRefund(orderID uint64, refundAmountCent int64, bizType
|
||||
})
|
||||
if err != nil {
|
||||
_ = r.markRefundFailed(refundOrder.ID, orderID, refundAmountCent, map[string]string{"error": err.Error()})
|
||||
log.Printf("[payment] refund request failed order_id=%d payment_id=%d biz_type=%s provider=%s amount_cent=%d err=%v",
|
||||
orderID, refundOrder.ID, bizType, runtimeConfig.Provider, refundAmountCent, err)
|
||||
return nil, err
|
||||
}
|
||||
if !resp.OK {
|
||||
_ = r.markRefundFailed(refundOrder.ID, orderID, refundAmountCent, resp.Raw)
|
||||
log.Printf("[payment] refund rejected order_id=%d payment_id=%d biz_type=%s provider=%s amount_cent=%d code=%s message=%s",
|
||||
orderID, refundOrder.ID, bizType, runtimeConfig.Provider, refundAmountCent, firstNonEmpty(resp.Raw["code"], resp.Raw["resp_code"], resp.Raw["result_code"]), resp.ErrorMessage)
|
||||
return nil, ErrPaymentUnavailable
|
||||
}
|
||||
|
||||
@@ -522,6 +544,8 @@ func (r *Repository) StartRefund(orderID uint64, refundAmountCent int64, bizType
|
||||
}
|
||||
refundOrder.Status = refundStatus
|
||||
refundOrder.ProviderOrderID = resp.ProviderRefundID
|
||||
log.Printf("[payment] refund result order_id=%d payment_id=%d biz_type=%s provider=%s amount_cent=%d status=%s provider_refund_id=%s",
|
||||
orderID, refundOrder.ID, bizType, runtimeConfig.Provider, refundAmountCent, refundStatus, resp.ProviderRefundID)
|
||||
|
||||
dto := toRefundDTO(refundOrder)
|
||||
return &dto, nil
|
||||
@@ -581,6 +605,51 @@ func (r *Repository) QueryRefundStatus(orderID uint64) (*RefundDTO, error) {
|
||||
return &dto, nil
|
||||
}
|
||||
|
||||
func (r *Repository) AdminList(query AdminPaymentQuery) (*PaginatedResult, error) {
|
||||
db := r.db.Table("payment_orders AS p").
|
||||
Select("p.*, COALESCE(u.phone, '') AS user_phone").
|
||||
Joins("LEFT JOIN users AS u ON u.id = p.user_id")
|
||||
countDB := r.db.Model(&model.PaymentOrder{})
|
||||
if query.UserID > 0 {
|
||||
db = db.Where("p.user_id = ?", query.UserID)
|
||||
countDB = countDB.Where("user_id = ?", query.UserID)
|
||||
}
|
||||
if query.OrderID > 0 {
|
||||
db = db.Where("p.order_id = ?", query.OrderID)
|
||||
countDB = countDB.Where("order_id = ?", query.OrderID)
|
||||
}
|
||||
if query.OrderNo != "" {
|
||||
db = db.Where("p.order_no = ?", query.OrderNo)
|
||||
countDB = countDB.Where("order_no = ?", query.OrderNo)
|
||||
}
|
||||
if query.BizType != "" {
|
||||
db = db.Where("p.biz_type = ?", query.BizType)
|
||||
countDB = countDB.Where("biz_type = ?", query.BizType)
|
||||
}
|
||||
if query.Status != "" {
|
||||
db = db.Where("p.status = ?", query.Status)
|
||||
countDB = countDB.Where("status = ?", query.Status)
|
||||
}
|
||||
if query.Provider != "" {
|
||||
db = db.Where("p.provider = ?", query.Provider)
|
||||
countDB = countDB.Where("provider = ?", query.Provider)
|
||||
}
|
||||
var total int64
|
||||
if err := countDB.Count(&total).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
offset := (query.Page - 1) * query.PageSize
|
||||
var rows []adminPaymentRow
|
||||
if err := db.Order("p.id DESC").Offset(offset).Limit(query.PageSize).Scan(&rows).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items := make([]AdminPaymentDTO, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
items = append(items, row.toDTO())
|
||||
}
|
||||
return &PaginatedResult{Items: items, Total: total, Page: query.Page, PageSize: query.PageSize}, nil
|
||||
}
|
||||
|
||||
// HandleRefundNotify 处理渠道退款通知。
|
||||
func (r *Repository) HandleRefundNotify(provider string, params map[string]string, rawPayload string, contentType string, authorization string) (*NotifyResult, error) {
|
||||
payment, err := r.findRefundPaymentForNotify(params)
|
||||
@@ -1004,6 +1073,55 @@ func toDTO(payment model.PaymentOrder) PaymentDTO {
|
||||
}
|
||||
}
|
||||
|
||||
type adminPaymentRow struct {
|
||||
model.PaymentOrder
|
||||
UserPhone string
|
||||
}
|
||||
|
||||
func (row adminPaymentRow) toDTO() AdminPaymentDTO {
|
||||
errorCode, errorMessage := paymentErrorSummary(row.Status, row.RawResponse)
|
||||
return AdminPaymentDTO{
|
||||
ID: row.ID,
|
||||
PaymentNo: row.PaymentNo,
|
||||
OrderID: row.OrderID,
|
||||
OrderNo: row.OrderNo,
|
||||
UserID: row.UserID,
|
||||
UserPhone: row.UserPhone,
|
||||
Provider: row.Provider,
|
||||
MerchantID: row.MerchantID,
|
||||
ThirdOrderID: row.ThirdOrderID,
|
||||
ProviderOrderID: row.ProviderOrderID,
|
||||
PayWay: row.PayWay,
|
||||
AmountCent: row.AmountCent,
|
||||
BizType: row.BizType,
|
||||
Status: row.Status,
|
||||
ErrorCode: errorCode,
|
||||
ErrorMessage: errorMessage,
|
||||
RawRequest: row.RawRequest,
|
||||
RawResponse: row.RawResponse,
|
||||
PaidAt: row.PaidAt,
|
||||
NotifiedAt: row.NotifiedAt,
|
||||
CreatedAt: row.CreatedAt,
|
||||
UpdatedAt: row.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func paymentErrorSummary(status string, raw datatypes.JSON) (string, string) {
|
||||
if status != "failed" {
|
||||
return "", ""
|
||||
}
|
||||
if len(raw) == 0 {
|
||||
return "", ""
|
||||
}
|
||||
var payload map[string]any
|
||||
if err := json.Unmarshal(raw, &payload); err != nil {
|
||||
return "", ""
|
||||
}
|
||||
code := firstStringValue(payload, "code", "resp_code", "result_code", "error_code", "status")
|
||||
message := firstStringValue(payload, "msg", "message", "error", "error_message", "result_msg", "result_desc")
|
||||
return code, message
|
||||
}
|
||||
|
||||
func moneyCent(value float64) int64 {
|
||||
return int64(math.Round(value * 100))
|
||||
}
|
||||
@@ -1014,6 +1132,69 @@ func parseCent(value string) int64 {
|
||||
return amount
|
||||
}
|
||||
|
||||
func refundOriginProviderOrderID(payment model.PaymentOrder) string {
|
||||
if payment.Provider != "lakala" {
|
||||
return payment.ProviderOrderID
|
||||
}
|
||||
if tradeID := lakalaOriginTradeID(payment.RawResponse); tradeID != "" {
|
||||
return tradeID
|
||||
}
|
||||
return payment.ProviderOrderID
|
||||
}
|
||||
|
||||
func lakalaOriginTradeID(raw datatypes.JSON) string {
|
||||
if len(raw) == 0 {
|
||||
return ""
|
||||
}
|
||||
var payload map[string]any
|
||||
if err := json.Unmarshal(raw, &payload); err != nil {
|
||||
return ""
|
||||
}
|
||||
if tradeID := firstStringValue(payload, "trade_no", "origin_trade_no"); tradeID != "" {
|
||||
return tradeID
|
||||
}
|
||||
value, ok := payload["order_trade_info_list"]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
switch typed := value.(type) {
|
||||
case string:
|
||||
var items []map[string]any
|
||||
if err := json.Unmarshal([]byte(typed), &items); err != nil {
|
||||
return ""
|
||||
}
|
||||
for _, item := range items {
|
||||
if tradeID := firstStringValue(item, "trade_no", "origin_trade_no"); tradeID != "" {
|
||||
return tradeID
|
||||
}
|
||||
}
|
||||
case []any:
|
||||
for _, item := range typed {
|
||||
itemMap, ok := item.(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if tradeID := firstStringValue(itemMap, "trade_no", "origin_trade_no"); tradeID != "" {
|
||||
return tradeID
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func firstStringValue(values map[string]any, keys ...string) string {
|
||||
for _, key := range keys {
|
||||
value, ok := values[key]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if text, ok := value.(string); ok && text != "" {
|
||||
return text
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func parseChannelTime(value string) *time.Time {
|
||||
if value == "" {
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user