初始提交:岩美 pay 收款服务(支付宝当面付 + 多商户多渠道架构)
- Go/Gin/GORM + 纯 Go SQLite(无 cgo) - Channel 多渠道接口:支付宝当面付(precreate)/电脑网站支付(page.pay) 已实现,微信占位 - 多商户 merchants 表,回调验签+金额核对+幂等+查单兜底 - 收款页/结果页/二维码端点;docs/ 设计文档与部署 Runbook - 密钥走环境变量/Bitwarden,不入库 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// NewOutTradeNo 生成全局唯一的商户订单号:<code>-<时间>-<随机>,控制在 64 字符内。
|
||||
// 例:yanmei-20260624153012-a1b2c3d4
|
||||
func NewOutTradeNo(merchantCode string) string {
|
||||
code := merchantCode
|
||||
if len(code) > 16 {
|
||||
code = code[:16]
|
||||
}
|
||||
ts := time.Now().Format("20060102150405")
|
||||
suffix := strings.ReplaceAll(uuid.NewString(), "-", "")[:8]
|
||||
return fmt.Sprintf("%s-%s-%s", code, ts, suffix)
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// AmountToCents 把 "0.01" / "12.30" 元金额转为分(int64),便于精确比较。
|
||||
func AmountToCents(s string) (int64, error) {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" {
|
||||
return 0, fmt.Errorf("空金额")
|
||||
}
|
||||
f, err := strconv.ParseFloat(s, 64)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("金额格式错误 %q: %w", s, err)
|
||||
}
|
||||
if f < 0 {
|
||||
return 0, fmt.Errorf("金额不能为负: %q", s)
|
||||
}
|
||||
return int64(math.Round(f * 100)), nil
|
||||
}
|
||||
|
||||
// AmountEqual 判断两个元金额字符串是否等值(按分比较,避免浮点/格式差异)。
|
||||
func AmountEqual(a, b string) bool {
|
||||
ca, err1 := AmountToCents(a)
|
||||
cb, err2 := AmountToCents(b)
|
||||
return err1 == nil && err2 == nil && ca == cb
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// RespondError 结构化错误:{"code":..,"message":..}
|
||||
func RespondError(c *gin.Context, status int, code, msg string) {
|
||||
c.JSON(status, gin.H{"code": code, "message": msg})
|
||||
}
|
||||
|
||||
// RespondSuccess 成功:{"data":..}
|
||||
func RespondSuccess(c *gin.Context, data interface{}) {
|
||||
c.JSON(http.StatusOK, gin.H{"data": data})
|
||||
}
|
||||
Reference in New Issue
Block a user