初始提交:岩美 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,124 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/glebarez/sqlite"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
|
||||
"github.com/wangjia/pay/config"
|
||||
"github.com/wangjia/pay/internal/channel"
|
||||
"github.com/wangjia/pay/internal/model"
|
||||
"github.com/wangjia/pay/internal/router"
|
||||
)
|
||||
|
||||
func main() {
|
||||
config.Load()
|
||||
|
||||
db := initDB()
|
||||
autoMigrate(db)
|
||||
seed(db)
|
||||
|
||||
reg := channel.NewRegistry(db)
|
||||
|
||||
gin.SetMode(config.C.Server.Mode)
|
||||
r := gin.New()
|
||||
r.Use(gin.Logger(), gin.Recovery())
|
||||
|
||||
orderSvc := router.Setup(r, db, reg)
|
||||
|
||||
if config.C.QuerySync.Enabled {
|
||||
orderSvc.StartQuerySync(
|
||||
time.Duration(config.C.QuerySync.IntervalSec)*time.Second,
|
||||
time.Duration(config.C.QuerySync.MaxAgeMin)*time.Minute,
|
||||
)
|
||||
log.Printf("查单兜底已启动:每 %ds 一次", config.C.QuerySync.IntervalSec)
|
||||
}
|
||||
|
||||
addr := ":" + config.C.Server.Port
|
||||
log.Printf("支付服务启动 %s (mode=%s, base_url=%s)", addr, config.C.Server.Mode, config.C.Server.BaseURL)
|
||||
if err := r.Run(addr); err != nil {
|
||||
log.Fatalf("启动失败: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func initDB() *gorm.DB {
|
||||
logLevel := logger.Silent
|
||||
if config.C.Server.Mode == "debug" {
|
||||
logLevel = logger.Info
|
||||
}
|
||||
gormCfg := &gorm.Config{Logger: logger.Default.LogMode(logLevel), TranslateError: true}
|
||||
|
||||
switch config.C.Database.Driver {
|
||||
case "sqlite", "":
|
||||
db, err := gorm.Open(sqlite.Open(config.C.Database.DSN), gormCfg)
|
||||
if err != nil {
|
||||
log.Fatalf("连接 sqlite 失败: %v", err)
|
||||
}
|
||||
return db
|
||||
case "mysql":
|
||||
log.Fatal("mysql 驱动尚未启用:go get gorm.io/driver/mysql 后在 initDB 接入")
|
||||
return nil
|
||||
default:
|
||||
log.Fatalf("未知 database.driver: %s", config.C.Database.Driver)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func autoMigrate(db *gorm.DB) {
|
||||
if err := db.AutoMigrate(
|
||||
&model.Merchant{},
|
||||
&model.Product{},
|
||||
&model.Order{},
|
||||
&model.NotifyLog{},
|
||||
); err != nil {
|
||||
log.Fatalf("自动迁移失败: %v", err)
|
||||
}
|
||||
log.Println("AutoMigrate 完成")
|
||||
}
|
||||
|
||||
// seed 据 config 的 alipay_sandbox upsert 一个支付宝商户,并为其补两个测试套餐。
|
||||
func seed(db *gorm.DB) {
|
||||
sb := config.C.AlipaySandbox
|
||||
if !sb.Enabled {
|
||||
log.Println("[seed] alipay_sandbox.enabled=false,跳过沙箱商户初始化")
|
||||
return
|
||||
}
|
||||
var m model.Merchant
|
||||
err := db.Where("code = ?", sb.MerchantCode).First(&m).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
m = model.Merchant{
|
||||
Code: sb.MerchantCode,
|
||||
Name: sb.MerchantName,
|
||||
Channel: "alipay",
|
||||
}
|
||||
} else if err != nil {
|
||||
log.Fatalf("[seed] 查询商户失败: %v", err)
|
||||
}
|
||||
m.Production = sb.Production
|
||||
m.Enabled = true
|
||||
m.AppID = sb.AppID
|
||||
m.AppPrivateKey = sb.AppPrivateKey
|
||||
m.AlipayPublicKey = sb.AlipayPublicKey
|
||||
if err := db.Save(&m).Error; err != nil {
|
||||
log.Fatalf("[seed] 保存商户失败: %v", err)
|
||||
}
|
||||
log.Printf("[seed] 支付宝沙箱商户就绪: code=%s app_id=%s", m.Code, m.AppID)
|
||||
|
||||
var cnt int64
|
||||
db.Model(&model.Product{}).Where("merchant_id = ?", m.ID).Count(&cnt)
|
||||
if cnt == 0 {
|
||||
samples := []model.Product{
|
||||
{MerchantID: m.ID, Name: "测试套餐 A", Description: "沙箱联调用", Price: "0.01", Active: true, Sort: 1},
|
||||
{MerchantID: m.ID, Name: "测试套餐 B", Description: "沙箱联调用", Price: "0.02", Active: true, Sort: 2},
|
||||
}
|
||||
if err := db.Create(&samples).Error; err != nil {
|
||||
log.Fatalf("[seed] 创建测试套餐失败: %v", err)
|
||||
}
|
||||
log.Printf("[seed] 已为商户 %s 创建 %d 个测试套餐", m.Code, len(samples))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user