Files
pay/main.go
T
wangjia 6da6964451 初始提交:岩美 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>
2026-06-25 07:21:48 +08:00

125 lines
3.3 KiB
Go

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))
}
}