Files

62 lines
2.6 KiB
Go

package payment
import (
"context"
"errors"
"time"
"git.apinb.com/heqiapp/platforms/backend/api/internal/config"
"git.apinb.com/heqiapp/platforms/backend/api/internal/impl"
"git.apinb.com/heqiapp/platforms/backend/api/internal/models"
"gorm.io/gorm"
)
const (
StatusPending = 10
StatusPaid = 23
StatusClosed = 30
)
type CreateInput struct {
RequestNo, BusinessType, BusinessIdentity, UserIdentity string
Channel, PayType, Subject, OpenID string
Amount int64
}
// Create 创建幂等支付单并向渠道请求客户端调起参数。
func Create(ctx context.Context, input CreateInput) (models.PaymentOrder, error) {
if input.Amount <= 0 || input.RequestNo == "" || input.BusinessIdentity == "" || input.UserIdentity == "" {
return models.PaymentOrder{}, gorm.ErrInvalidData
}
var existing models.PaymentOrder
if err := impl.DBService.Where("business_type = ? AND request_no = ?", input.BusinessType, input.RequestNo).First(&existing).Error; err == nil {
if existing.BusinessIdentity != input.BusinessIdentity || existing.Amount != input.Amount || existing.Channel != input.Channel || existing.PayType != input.PayType {
return existing, errors.New("idempotency conflict")
}
return existing, nil
}
order := models.PaymentOrder{Entity: models.Entity{Identity: models.NewIdentity(), Status: 1}, PaymentStatus: StatusPending,
PaymentNo: "PAY" + time.Now().Format("20060102150405.000000"), RequestNo: input.RequestNo, BusinessType: input.BusinessType,
BusinessIdentity: input.BusinessIdentity, UserIdentity: input.UserIdentity, MerchantIdentity: "platform", Channel: input.Channel,
PayType: input.PayType, Amount: input.Amount, Subject: input.Subject, ExpiresAt: time.Now().Add(time.Duration(config.Spec.Payment.ExpireMinutes) * time.Minute)}
args, err := createChannelOrder(ctx, order, input.OpenID)
if err != nil {
return order, err
}
order.ClientArgs = args
if err = impl.DBService.Create(&order).Error; err != nil {
return order, err
}
return order, nil
}
// PublicResponse 仅返回客户端调起支付所需的非密钥参数。
func PublicResponse(order models.PaymentOrder) map[string]any {
response := map[string]any{"identity": order.Identity, "payment_no": order.PaymentNo, "payment_status": order.PaymentStatus,
"channel": order.Channel, "pay_type": order.PayType, "amount": order.Amount, "client_args": order.ClientArgs, "expires_at": order.ExpiresAt}
if order.Channel == "wechat" && order.PayType == "app" {
response["app_id"] = config.Spec.Payment.Wechat.AppAppID
}
return response
}