Commit 85ce55c3 by zhengqiuyun86

微信支付

parent 5fe915ab
package dao
import (
"dc_golang_server_1/dao/db"
baseModel "dc_golang_server_1/model/base"
)
func FindWechatAppConfigByPrimaryKey(appId string) (baseModel.WechatAppConfig, error) {
var model baseModel.WechatAppConfig
result := db.PgDb.First(&model, appId)
return model, result.Error
}
package dao
import (
"dc_golang_server_1/dao/db"
baseModel "dc_golang_server_1/model/base"
)
func FindWechatMchConfigByPrimaryKey(mchId string) (baseModel.WechatMchConfig, error) {
var model baseModel.WechatMchConfig
result := db.PgDb.First(&model, mchId)
return model, result.Error
}
package dao
import (
"dc_golang_server_1/dao/db"
baseModel "dc_golang_server_1/model/base"
)
func FindWechatPayConfigByPrimaryKey(areaServiceId uint32) (baseModel.WechatPayConfig, error) {
var model baseModel.WechatPayConfig
result := db.PgDb.First(&model, areaServiceId)
return model, result.Error
}
package dao package db
import ( import (
"dc_golang_server_1/config" "dc_golang_server_1/config"
dc_logger "dc_golang_server_1/logger"
"gorm.io/driver/postgres"
"gorm.io/gorm" "gorm.io/gorm"
"gorm.io/gorm/logger" "gorm.io/gorm/logger"
"gorm.io/driver/postgres"
"log" "log"
"os" "os"
"time" "time"
...@@ -13,22 +15,27 @@ import ( ...@@ -13,22 +15,27 @@ import (
var PgDb *gorm.DB var PgDb *gorm.DB
var NewLogger logger.Interface
func InitPgDb() { func InitPgDb() {
newLogger := logger.New( NewLogger = logger.New(
log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注) log.New(os.Stdout, " ", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注)
logger.Config{ logger.Config{
SlowThreshold: time.Second, // 慢 SQL 阈值 SlowThreshold: time.Second, // 慢 SQL 阈值
LogLevel: logger.Info, // 日志级别 LogLevel: logger.Error, // 日志级别
IgnoreRecordNotFoundError: true, // 忽略ErrRecordNotFound(记录未找到)错误 IgnoreRecordNotFoundError: true, // 忽略ErrRecordNotFound(记录未找到)错误
Colorful: false, // 禁用彩色打印 Colorful: true, // 禁用彩色打印
}, },
) )
var err error db, err := gorm.Open(postgres.Open(config.DbDSN), &gorm.Config{
dc_logger.Log.Info("初始化连接") Logger: NewLogger,
PgDb, err = gorm.Open(postgres.Open(config.DbDSN), &gorm.Config{
Logger: newLogger,
}) })
if err != nil { if err != nil {
dc_logger.Log.Error(err.Error()) log.Printf(err.Error())
} }
//if conf.ShowSql {
// PgDb = db.Debug()
//}else{
PgDb = db
//}
} }
package model
import (
"time"
)
type BArea struct {
Id uint32 `gorm:"column:id;not null" json:"id"`
CountyId uint32 `gorm:"column:county_id json:"countyId"`
Name string `gorm:"column:name json:"name"`
Location string `gorm:"column:location json:"location"`
Status uint16 `gorm:"column:status json:"status"`
CreatorId uint32 `gorm:"column:creator_id json:"creatorId"`
UpdaterId uint32 `gorm:"column:updater_id json:"updaterId"`
CreateAt time.Time `gorm:"column:create_at json:"createAt"`
UpdateAt time.Time `gorm:"column:update_at json:"updateAt"`
}
package model
import (
"time"
)
// 区域服务
type BAreaService struct {
Id uint32 `gorm:"column:id;not null" json:"id"`
ServiceType uint16 `gorm:"column:service_type json:"serviceType"`
Name string `gorm:"column:name json:"name"`
AreaId uint32 `gorm:"column:area_id json:"areaId"`
OperatorId uint32 `gorm:"column:operator_id json:"operatorId"`
Manager string `gorm:"column:manager json:"manager"`
ManagerPhone string `gorm:"column:manager_phone json:"managerPhone"`
Status uint16 `gorm:"column:status json:"status"`
CreatorId uint32 `gorm:"column:creator_id json:"creatorId"`
UpdaterId uint32 `gorm:"column:updater_id json:"updaterId"`
CreateAt time.Time `gorm:"column:create_at json:"createAt"`
UpdateAt time.Time `gorm:"column:update_at json:"updateAt"`
}
package model
import (
"time"
)
// 区域收款信息
type BAreaServiceCollection struct {
AreaServiceId uint32 `gorm:"column:area_service_id;not null" json:"areaServiceId"`
ReceiverId uint32 `gorm:"column:receiver_id;not null" json:"receiverId"`
Status bool `gorm:"column:status json:"status"`
Rate uint16 `gorm:"column:rate json:"rate"`
CreatorId uint32 `gorm:"column:creator_id json:"creatorId"`
UpdaterId uint32 `gorm:"column:updater_id json:"updaterId"`
CreateAt time.Time `gorm:"column:create_at json:"createAt"`
UpdateAt time.Time `gorm:"column:update_at json:"updateAt"`
}
package model
// 省市区
type BCity struct {
Id uint32 `gorm:"column:id;not null" json:"id"`
Name string `gorm:"column:name json:"name"`
ParentId uint32 `gorm:"column:parent_id json:"parentId"`
Level uint16 `gorm:"column:level json:"level"`
Initial string `gorm:"column:initial json:"initial"` // 首字母
Sort uint32 `gorm:"column:sort json:"sort"` // 排序
}
package model
import (
"time"
)
// 服务商
type BOperator struct {
Id uint32 `gorm:"column:id;not null" json:"id"`
Name string `gorm:"column:name json:"name"`
ShortName string `gorm:"column:short_name json:"shortName"`
CountyId uint32 `gorm:"column:county_id json:"countyId"`
Address string `gorm:"column:address json:"address"`
LegalPerson string `gorm:"column:legal_person json:"legalPerson"`
LegalPhone string `gorm:"column:legal_phone json:"legalPhone"`
AptitudeUrl string `gorm:"column:aptitude_url json:"aptitudeUrl"`
CreatorId uint32 `gorm:"column:creator_id json:"creatorId"`
UpdaterId uint32 `gorm:"column:updater_id json:"updaterId"`
CreateAt time.Time `gorm:"column:create_at json:"createAt"`
UpdateAt time.Time `gorm:"column:update_at json:"updateAt"`
}
package model
import (
"time"
)
// 微信应用配置
type BWechatAppConfig struct {
Appid uint32 `gorm:"column:appid;not null" json:"appid"`
Name string `gorm:"column:name json:"name"`
Secret string `gorm:"column:secret json:"secret"`
CreatorId uint32 `gorm:"column:creator_id json:"creatorId"`
UpdaterId uint32 `gorm:"column:updater_id json:"updaterId"`
CreateAt time.Time `gorm:"column:create_at json:"createAt"`
UpdateAt time.Time `gorm:"column:update_at json:"updateAt"`
}
package model
import (
"time"
)
// 微信商户配置
type BWechatMchConfig struct {
Mchid string `gorm:"column:mchid;not null" json:"mchid"`
Name string `gorm:"column:name json:"name"`
ApiV3Key string `gorm:"column:api_v3_key json:"apiV3Key"`
MchSerialNo string `gorm:"column:mch_serial_no json:"mchSerialNo"`
PrivateKey string `gorm:"column:private_key json:"privateKey"`
CreatorId uint32 `gorm:"column:creator_id json:"creatorId"`
UpdaterId uint32 `gorm:"column:updater_id json:"updaterId"`
CreateAt time.Time `gorm:"column:create_at json:"createAt"`
UpdateAt time.Time `gorm:"column:update_at json:"updateAt"`
}
package model
import (
"time"
)
// 微信支付配置
type BWechatPayConfig struct {
Id uint32 `gorm:"column:id;not null" json:"id"`
AreaServiceId uint32 `gorm:"column:area_service_id json:"areaServiceId"`
SpAppid string `gorm:"column:sp_appid json:"spAppid"`
SpMchid string `gorm:"column:sp_mchid json:"spMchid"`
SubMchid string `gorm:"column:sub_mchid json:"subMchid"`
CreatorId uint32 `gorm:"column:creator_id json:"creatorId"`
UpdaterId uint32 `gorm:"column:updater_id json:"updaterId"`
CreateAt time.Time `gorm:"column:create_at json:"createAt"`
UpdateAt time.Time `gorm:"column:update_at json:"updateAt"`
}
package model
import (
"time"
)
// 微信分账接收方
type BWechatProfitSharingReceiver struct {
Id uint32 `gorm:"column:id;not null" json:"id"`
Type string `gorm:"column:type json:"type"`
Account string `gorm:"column:account json:"account"`
Name string `gorm:"column:name json:"name"`
CreatorId uint32 `gorm:"column:creator_id json:"creatorId"`
UpdaterId uint32 `gorm:"column:updater_id json:"updaterId"`
CreateAt time.Time `gorm:"column:create_at json:"createAt"`
UpdateAt time.Time `gorm:"column:update_at json:"updateAt"`
}
package model
import (
"time"
)
// 后台用户
type SAdmin struct {
Id uint32 `gorm:"column:id;not null" json:"id"`
LoginAccount string `gorm:"column:login_account json:"loginAccount"`
Password string `gorm:"column:password json:"password"`
Name string `gorm:"column:name json:"name"`
SysRoleId uint32 `gorm:"column:sys_role_id json:"sysRoleId"`
ParentId uint32 `gorm:"column:parent_id json:"parentId"`
CreatorId uint32 `gorm:"column:creator_id json:"creatorId"`
UpdaterId uint32 `gorm:"column:updater_id json:"updaterId"`
CreateAt time.Time `gorm:"column:create_at json:"createAt"`
UpdateAt time.Time `gorm:"column:update_at json:"updateAt"`
}
package model
import (
"time"
)
// 登录流水
type SAdminLogin struct {
AdminId uint32 `gorm:"column:admin_id json:"adminId"`
Ip string `gorm:"column:ip json:"ip"`
CreateAt time.Time `gorm:"column:create_at json:"createAt"`
}
package model
import (
"time"
)
// 用户区域角色
type SAdminRole struct {
AdminId uint32 `gorm:"column:admin_id;not null" json:"adminId"`
RoleId uint32 `gorm:"column:role_id;not null" json:"roleId"`
AreaServiceId uint32 `gorm:"column:area_service_id;not null" json:"areaServiceId"`
CreatorId uint32 `gorm:"column:creator_id json:"creatorId"`
UpdaterId uint32 `gorm:"column:updater_id json:"updaterId"`
CreateAt time.Time `gorm:"column:create_at json:"createAt"`
UpdateAt time.Time `gorm:"column:update_at json:"updateAt"`
}
package model
import (
"time"
)
// api接口
type SApi struct {
Id uint32 `gorm:"column:id;not null" json:"id"`
ApiPath string `gorm:"column:api_path json:"apiPath"`
ApiMethod string `gorm:"column:api_method json:"apiMethod"`
CreatorId uint32 `gorm:"column:creator_id json:"creatorId"`
UpdaterId uint32 `gorm:"column:updater_id json:"updaterId"`
CreateAt time.Time `gorm:"column:create_at json:"createAt"`
UpdateAt time.Time `gorm:"column:update_at json:"updateAt"`
}
package model
import (
"time"
)
// 菜单及按钮
type SMenuBtn struct {
Id uint32 `gorm:"column:id;not null" json:"id"`
Name string `gorm:"column:name json:"name"`
Status bool `gorm:"column:status json:"status"`
Explain string `gorm:"column:explain json:"explain"`
ParentId uint32 `gorm:"column:parent_id json:"parentId"`
CreatorId uint32 `gorm:"column:creator_id json:"creatorId"`
UpdaterId uint32 `gorm:"column:updater_id json:"updaterId"`
CreateAt time.Time `gorm:"column:create_at json:"createAt"`
UpdateAt time.Time `gorm:"column:update_at json:"updateAt"`
}
package model
import (
"time"
)
// 角色
type SRole struct {
Id uint32 `gorm:"column:id;not null" json:"id"`
Name string `gorm:"column:name json:"name"`
CreatorId uint32 `gorm:"column:creator_id json:"creatorId"`
UpdaterId uint32 `gorm:"column:updater_id json:"updaterId"`
CreateAt time.Time `gorm:"column:create_at json:"createAt"`
UpdateAt time.Time `gorm:"column:update_at json:"updateAt"`
}
package model
import (
"time"
)
// 角色接口权限
type SRoleApiAuth struct {
RoleId uint32 `gorm:"column:role_id;not null" json:"roleId"`
ApiId uint32 `gorm:"column:api_id;not null" json:"apiId"`
CreatorId uint32 `gorm:"column:creator_id json:"creatorId"`
UpdaterId uint32 `gorm:"column:updater_id json:"updaterId"`
CreateAt time.Time `gorm:"column:create_at json:"createAt"`
UpdateAt time.Time `gorm:"column:update_at json:"updateAt"`
}
package model
import (
"time"
)
// 角色菜单权限
type SRoleMenuAuth struct {
RoleId uint32 `gorm:"column:role_id;not null" json:"roleId"`
MenuId uint32 `gorm:"column:menu_id;not null" json:"menuId"`
CreatorId uint32 `gorm:"column:creator_id json:"creatorId"`
UpdaterId uint32 `gorm:"column:updater_id json:"updaterId"`
CreateAt time.Time `gorm:"column:create_at json:"createAt"`
UpdateAt time.Time `gorm:"column:update_at json:"updateAt"`
}
package exception
import (
"errors"
)
func ThrowsErr(err error) {
if err != nil {
panic(err)
}
}
func ThrowsErrS(err string) {
if &err != nil && err != "" {
panic(errors.New(err))
}
}
package catch
import (
"dc_golang_server_1/web/response"
"github.com/gin-gonic/gin"
"net/http"
)
func ExceptionCatch(c *gin.Context) {
err := recover() //获取异常
if err != nil {
e := (err).(error)
msg := ErrorMapping[e.Error()]
if &msg == nil || msg == "" {
c.JSON(http.StatusOK, response.FailAndMsg(e.Error()))
} else {
c.JSON(http.StatusOK, response.FailAndMsg(msg))
}
}
}
type ErrorMap map[string]string
var ErrorMapping = ErrorMap{
"": "信息不存在",
"unexpected EOF": "参数格式不正确",
}
...@@ -10,6 +10,7 @@ require ( ...@@ -10,6 +10,7 @@ require (
github.com/natefinch/lumberjack v2.0.0+incompatible github.com/natefinch/lumberjack v2.0.0+incompatible
github.com/swaggo/gin-swagger v1.3.2 github.com/swaggo/gin-swagger v1.3.2
github.com/swaggo/swag v1.7.3 github.com/swaggo/swag v1.7.3
github.com/wechatpay-apiv3/wechatpay-go v0.2.9
go.uber.org/zap v1.19.1 go.uber.org/zap v1.19.1
gopkg.in/ini.v1 v1.63.2 gopkg.in/ini.v1 v1.63.2
gorm.io/driver/postgres v1.1.2 gorm.io/driver/postgres v1.1.2
......
...@@ -10,6 +10,8 @@ github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tN ...@@ -10,6 +10,8 @@ github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tN
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/agiledragon/gomonkey v2.0.2+incompatible h1:eXKi9/piiC3cjJD1658mEE2o3NjkJ5vDLgYjCQu0Xlw=
github.com/agiledragon/gomonkey v2.0.2+incompatible/go.mod h1:2NGfXu1a80LLr2cmWXGBDaHEjb1idR6+FVlX5T3D9hw=
github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394 h1:OYA+5W64v3OgClL+IrOD63t4i/RW7RqrAVl9LTZ9UqQ= github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394 h1:OYA+5W64v3OgClL+IrOD63t4i/RW7RqrAVl9LTZ9UqQ=
github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394/go.mod h1:Q8n74mJTIgjX4RBBcHnJ05h//6/k6foqmgE45jTQtxg= github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394/go.mod h1:Q8n74mJTIgjX4RBBcHnJ05h//6/k6foqmgE45jTQtxg=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
...@@ -226,6 +228,8 @@ github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ ...@@ -226,6 +228,8 @@ github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ
github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw= github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/wechatpay-apiv3/wechatpay-go v0.2.9 h1:FnFdYLquHWEB0pBacOHC9BePgXcf26vZfn2X3uYbo0c=
github.com/wechatpay-apiv3/wechatpay-go v0.2.9/go.mod h1:W8ucVAOCKOii933cWROLaDLmRQ2cg/vHHVF4vGAVq9Q=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
......
...@@ -2,10 +2,10 @@ package main ...@@ -2,10 +2,10 @@ package main
import ( import (
"dc_golang_server_1/config" "dc_golang_server_1/config"
"dc_golang_server_1/dao/db"
"dc_golang_server_1/devproduct/dcphone20" "dc_golang_server_1/devproduct/dcphone20"
"dc_golang_server_1/logger" "dc_golang_server_1/logger"
"dc_golang_server_1/web" "dc_golang_server_1/web"
"dc_golang_server_1/wechat/dao"
"fmt" "fmt"
"runtime" "runtime"
"time" "time"
...@@ -18,7 +18,7 @@ import ( ...@@ -18,7 +18,7 @@ import (
func initConfigAndLogger() { func initConfigAndLogger() {
config.ConfigInit() config.ConfigInit()
logger.InitLogger() logger.InitLogger()
dao.InitPgDb() db.InitPgDb()
//突然发现这种方式瓜眉日眼的,以后不用了,直接在shell脚本里面重定向输出文件 //突然发现这种方式瓜眉日眼的,以后不用了,直接在shell脚本里面重定向输出文件
//f, err := os.OpenFile(config.LogPath+"panic.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0755) //f, err := os.OpenFile(config.LogPath+"panic.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0755)
//if err != nil { //if err != nil {
......
package model
import (
"dc_golang_server_1/model/conver"
)
func (WechatAppConfig) TableName() string {
return "b_wechat_app_config"
}
// WechatAppConfig 微信应用配置
type WechatAppConfig struct {
AppId *string `gorm:"primaryKey:app_id" json:"appId,omitempty"` // APPID
Name *string `gorm:"column:name" json:"name,omitempty"` // 应用名称
Secret *string `gorm:"column:secret" json:"secret,omitempty"` // 微信应用secret
CreatorId *uint32 `gorm:"column:creator_id" json:"creatorId,omitempty"` // 创建人
UpdaterId *uint32 `gorm:"column:updater_id" json:"updaterId,omitempty"` // 修改人
CreateAt *conver.Time `gorm:"column:create_at" json:"createAt,omitempty"` // 创建时间
UpdateAt *conver.Time `gorm:"column:update_at" json:"updateAt,omitempty"` // 修改时间
}
type WechatAppConfigColumnsStruct struct {
AppId string
Name string
Secret string
CreatorId string
UpdaterId string
CreateAt string
UpdateAt string
}
var WechatAppConfigColumns = WechatAppConfigColumnsStruct{
AppId: "app_id",
Name: "name",
Secret: "secret",
CreatorId: "creator_id",
UpdaterId: "updater_id",
CreateAt: "create_at",
UpdateAt: "update_at",
}
package model
import (
"dc_golang_server_1/model/conver"
)
func (WechatMchConfig) TableName() string {
return "b_wechat_mch_config"
}
// WechatMchConfig 微信商户配置
type WechatMchConfig struct {
MchId *string `gorm:"primaryKey:mch_id" json:"mchId,omitempty"` // 商户号
Name *string `gorm:"column:name" json:"name,omitempty"` // 商户名称
ApiV3Key *string `gorm:"column:api_v3_key" json:"apiV3Key,omitempty"` // ApiV3Key
MchSerialNo *string `gorm:"column:mch_serial_no" json:"mchSerialNo,omitempty"` // 商户证书序号
PrivateKey *string `gorm:"column:private_key" json:"privateKey,omitempty"` // 商户私钥
CreatorId *uint32 `gorm:"column:creator_id" json:"creatorId,omitempty"` // 创建人
UpdaterId *uint32 `gorm:"column:updater_id" json:"updaterId,omitempty"` // 修改人
CreateAt *conver.Time `gorm:"column:create_at" json:"createAt,omitempty"` // 创建时间
UpdateAt *conver.Time `gorm:"column:update_at" json:"updateAt,omitempty"` // 修改时间
}
type WechatMchConfigColumnsStruct struct {
MchId string
Name string
ApiV3Key string
MchSerialNo string
PrivateKey string
CreatorId string
UpdaterId string
CreateAt string
UpdateAt string
}
var WechatMchConfigColumns = WechatMchConfigColumnsStruct{
MchId: "mch_id",
Name: "name",
ApiV3Key: "api_v3_key",
MchSerialNo: "mch_serial_no",
PrivateKey: "private_key",
CreatorId: "creator_id",
UpdaterId: "updater_id",
CreateAt: "create_at",
UpdateAt: "update_at",
}
package model
import (
"dc_golang_server_1/model/conver"
)
func (WechatPayConfig) TableName() string {
return "b_wechat_pay_config"
}
// WechatPayConfig 微信支付配置
type WechatPayConfig struct {
AreaServiceId *uint32 `gorm:"primaryKey:area_service_id" json:"areaServiceId,omitempty"` // 区域服务ID
SpAppid *string `gorm:"column:sp_appid" json:"spAppid,omitempty"` // 服务商APPID
SpMchid *string `gorm:"column:sp_mchid" json:"spMchid,omitempty"` // 服务商商户号
SubMchid *string `gorm:"column:sub_mchid" json:"subMchid,omitempty"` // 特约商户号
CreatorId *uint32 `gorm:"column:creator_id" json:"creatorId,omitempty"` // 创建人
UpdaterId *uint32 `gorm:"column:updater_id" json:"updaterId,omitempty"` // 修改人
CreateAt *conver.Time `gorm:"column:create_at" json:"createAt,omitempty"` // 创建时间
UpdateAt *conver.Time `gorm:"column:update_at" json:"updateAt,omitempty"` // 修改时间
}
type WechatPayConfigColumnsStruct struct {
AreaServiceId string
SpAppid string
SpMchid string
SubMchid string
CreatorId string
UpdaterId string
CreateAt string
UpdateAt string
}
var WechatPayConfigColumns = WechatPayConfigColumnsStruct{
AreaServiceId: "area_service_id",
SpAppid: "sp_appid",
SpMchid: "sp_mchid",
SubMchid: "sub_mchid",
CreatorId: "creator_id",
UpdaterId: "updater_id",
CreateAt: "create_at",
UpdateAt: "update_at",
}
package conver
import (
"database/sql/driver"
"errors"
"fmt"
"strings"
"time"
)
//Time 自定义时间
type Time time.Time
func (t *Time) UnmarshalJSON(data []byte) error {
if string(data) == "null" {
return nil
}
var err error
//前端接收的时间字符串
str := string(data)
//去除接收的str收尾多余的"
timeStr := strings.Trim(str, "\"")
t1, err := time.Parse("2006-01-02 15:04:05", timeStr)
*t = Time(t1)
return err
}
func (t Time) MarshalJSON() ([]byte, error) {
formatted := fmt.Sprintf("\"%v\"", time.Time(t).Format("2006-01-02 15:04:05"))
return []byte(formatted), nil
}
func (t Time) Value() (driver.Value, error) {
// Time 转换成 time.Time 类型
tTime := time.Time(t)
return tTime.Format("2006-01-02 15:04:05"), nil
}
func (t *Time) Scan(v interface{}) error {
switch vt := v.(type) {
case time.Time:
// 字符串转成 time.Time 类型
*t = Time(vt)
default:
return errors.New("类型处理错误")
}
return nil
}
func (t *Time) String() string {
return fmt.Sprintf("hhh:%s", time.Time(*t).String())
}
func Now() Time {
return Time(time.Now())
}
package params
type WechatPartnerJsapiOrderParams struct {
AreaServiceId *uint32 `json:"areaServiceId,omitempty"` //区域服务id
Title *string `json:"title,omitempty"` //支付标题
OutTradeNo *string `json:"outTradeNo,omitempty"` //商户订单号
Amount *float64 `json:"amount,omitempty"` //订单金额,单位元,保留两位小数
OpenId *string `json:"openId,omitempty"` //微信小程序对应openId
}
type WechatPartnerRefundParams struct {
AreaServiceId *uint32 `json:"areaServiceId,omitempty"` //区域服务id
OutRefundNo *string `json:"outRefundNo,omitempty"` //退款单号
OutTradeNo *string `json:"outTradeNo,omitempty"` //原商户订单号
OrderAmount *float64 `json:"orderAmount,omitempty"` //原订单金额
RefundAmount *float64 `json:"refundAmount,omitempty"` //本次退款金额
}
package response
type WxMiniRequestPayment struct {
AppId string `json:"appId"`
TimeStamp string `json:"timeStamp"`
NonceStr string `json:"nonceStr"`
Package string `json:"package"`
SignType string `json:"signType"`
PaySign string `json:"paySign"`
}
type WxOAuth2ResponseData struct {
Appid string `json:"appId"`
Openid string `json:"openid"`
Unionid string `json:"unionid"`
Errcode string `json:"errcode"`
Errmsg string `json:"errmsg"`
}
package service
import (
baseDao "dc_golang_server_1/dao/base"
baseModel "dc_golang_server_1/model/base"
)
func FindWechatAppConfig(appId string) (baseModel.WechatAppConfig, error) {
return baseDao.FindWechatAppConfigByPrimaryKey(appId)
}
package service
import (
baseDao "dc_golang_server_1/dao/base"
baseModel "dc_golang_server_1/model/base"
)
func FindWechatMchConfig(mchId string) (baseModel.WechatMchConfig, error) {
return baseDao.FindWechatMchConfigByPrimaryKey(mchId)
}
package service
import (
baseDao "dc_golang_server_1/dao/base"
baseModel "dc_golang_server_1/model/base"
)
func FindWechatPayConfig(areaServiceId uint32) (baseModel.WechatPayConfig, error) {
return baseDao.FindWechatPayConfigByPrimaryKey(areaServiceId)
}
package wechat
import (
"bytes"
"context"
"crypto/rsa"
"dc_golang_server_1/exception"
baseModel "dc_golang_server_1/model/base"
baseService "dc_golang_server_1/service/base"
"github.com/wechatpay-apiv3/wechatpay-go/core"
"github.com/wechatpay-apiv3/wechatpay-go/core/auth/verifiers"
"github.com/wechatpay-apiv3/wechatpay-go/core/downloader"
"github.com/wechatpay-apiv3/wechatpay-go/core/notify"
"github.com/wechatpay-apiv3/wechatpay-go/core/option"
"github.com/wechatpay-apiv3/wechatpay-go/utils"
"log"
)
type WxClient struct {
Client *core.Client
MchConfig baseModel.WechatMchConfig
PrivateKey *rsa.PrivateKey
}
func GetHandler(mchID string) *notify.Handler {
wechatMchConfig, err := baseService.FindWechatMchConfig(mchID)
if err != nil {
exception.ThrowsErr(err)
}
if wechatMchConfig.MchId == nil {
exception.ThrowsErrS("[" + mchID + "]对应配置不存在,请检查")
}
// 获取平台证书访问器
certVisitor := downloader.MgrInstance().GetCertificateVisitor(mchID)
handler := notify.NewNotifyHandler(*wechatMchConfig.ApiV3Key, verifiers.NewSHA256WithRSAVerifier(certVisitor))
return handler
}
func BuildClient(mchID string) WxClient {
wechatMchConfig, err := baseService.FindWechatMchConfig(mchID)
if err != nil {
exception.ThrowsErr(err)
}
if wechatMchConfig.MchId == nil {
exception.ThrowsErrS("[" + mchID + "]对应配置不存在,请检查")
}
client, privateKey := initClientByMchID(mchID, *wechatMchConfig.MchSerialNo, *wechatMchConfig.ApiV3Key, *wechatMchConfig.PrivateKey)
return WxClient{client, wechatMchConfig, privateKey}
}
func initClientByMchID(mchID string, mchCertificateSerialNumber string, mchAPIv3Key string, mchPrivateKey string) (*core.Client, *rsa.PrivateKey) {
// 使用 utils 提供的函数从本地文件中加载商户私钥,商户私钥会用来生成请求的签名
//privateKey, err := utils.LoadPrivateKeyWithPath(PKeyPath)
privateKey := loadPrivateKey(mchPrivateKey)
ctx := context.Background()
// 使用商户私钥等初始化 client,并使它具有自动定时获取微信支付平台证书的能力
opts := []core.ClientOption{
option.WithWechatPayAutoAuthCipher(mchID, mchCertificateSerialNumber, privateKey, mchAPIv3Key),
}
wechatClient, err := core.NewClient(ctx, opts...)
if err != nil {
exception.ThrowsErr(err)
}
return wechatClient, privateKey
}
func loadPrivateKey(privateKeyStr string) *rsa.PrivateKey {
privateKey, err := utils.LoadPrivateKey(privateKeyStr)
if err != nil {
exception.ThrowsErr(err)
}
return privateKey
}
func SignPay(appId string, timeStamp string, nonceStr string, packageInfo string, privateKey *rsa.PrivateKey) string {
var data bytes.Buffer
data.WriteString(appId)
data.WriteString("\n")
data.WriteString(timeStamp)
data.WriteString("\n")
data.WriteString(nonceStr)
data.WriteString("\n")
data.WriteString(packageInfo)
data.WriteString("\n")
log.Println("需要签名的信息:" + data.String())
signStr, err := utils.SignSHA256WithRSA(data.String(), privateKey)
if err != nil {
exception.ThrowsErr(err)
}
return signStr
}
package wechat
import (
"context"
"encoding/json"
//"dc_golang_server_1/conf"
dao "dc_golang_server_1/dao/base"
"dc_golang_server_1/exception"
"dc_golang_server_1/model/wechat/params"
"dc_golang_server_1/model/wechat/response"
"dc_golang_server_1/util"
"github.com/wechatpay-apiv3/wechatpay-go/services/payments"
"github.com/wechatpay-apiv3/wechatpay-go/services/refunddomestic"
"io/ioutil"
"math"
"net/http"
"strconv"
"strings"
"time"
"github.com/wechatpay-apiv3/wechatpay-go/core"
"github.com/wechatpay-apiv3/wechatpay-go/services/partnerpayments/jsapi"
)
const MainHost = "" //回调通知的外网域名
func ToWxMoney(amount float64) *int64 {
return core.Int64(int64(math.Floor(amount * 100)))
}
//JsapiPartnerPrepayOrder 服务商模式小程序支付
func JsapiPartnerPrepayOrder(params params.WechatPartnerJsapiOrderParams) response.WxMiniRequestPayment {
wechatPayConfig, err := dao.FindWechatPayConfigByPrimaryKey(*params.AreaServiceId)
if wechatPayConfig.AreaServiceId == nil {
exception.ThrowsErrS("[" + strconv.Itoa(int(*params.AreaServiceId)) + "]对应微信支付配不存在")
}
wechatClient := BuildClient(*wechatPayConfig.SpMchid)
svc := jsapi.JsapiApiService{Client: wechatClient.Client}
// 得到prepay_id,以及调起支付所需的参数和签名
resp, _, err := svc.Prepay(context.Background(),
jsapi.PrepayRequest{
SpAppid: core.String(*wechatPayConfig.SpAppid),
SpMchid: core.String(*wechatPayConfig.SpMchid),
SubMchid: core.String(*wechatPayConfig.SubMchid),
Description: core.String(*params.Title),
OutTradeNo: core.String(*params.OutTradeNo),
NotifyUrl: core.String(MainHost + "/wechat/partner/pay/notify/" + *wechatPayConfig.SpMchid),
Amount: &jsapi.Amount{
Total: ToWxMoney(*params.Amount),
},
Payer: &jsapi.Payer{
SpOpenid: params.OpenId,
},
},
)
if err != nil {
exception.ThrowsErr(err)
}
return buildWxMiniPayInfo(*wechatPayConfig.SpAppid, *resp.PrepayId, wechatClient)
}
func buildWxMiniPayInfo(appId string, prepayId string, wechatClient WxClient) response.WxMiniRequestPayment {
var rd response.WxMiniRequestPayment
rd.AppId = appId
rd.TimeStamp = util.Strval(time.Now().Unix())
rd.NonceStr = util.FandomString(32)
rd.Package = "prepay_id=" + prepayId
rd.SignType = "RSA"
paySign := SignPay(rd.AppId, rd.TimeStamp, rd.NonceStr, rd.Package, wechatClient.PrivateKey)
rd.PaySign = paySign
return rd
}
func PayBack(mchID string, request *http.Request) bool {
handler := GetHandler(mchID)
transaction := new(payments.Transaction)
notifyReq, err := handler.ParseNotifyRequest(context.Background(), request, transaction)
if err != nil {
exception.ThrowsErr(err)
}
if notifyReq != nil {
if *transaction.TradeState == "SUCCESS" {
payBackBusiness(*transaction.OutTradeNo)
return true
}
}
return false
}
/**
支付回调处理业务
注意:
同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。 推荐的做法是,当商户系统收到通知进行处理时,先检查对应业务数据的状态,并判断该通知是否已经处理。如果未处理,则再进行处理;如果已处理,则直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。
*/
func payBackBusiness(outTradeNo string) {
}
type RefundNotifyData struct {
SpMchId *string `json:"sp_mchid,omitempty"`
SubMchid *string `json:"sub_mchid,omitempty"`
TransactionId *string `json:"transaction_id,omitempty"`
OutTradeNo *string `json:"out_trade_no,omitempty"`
RefundId *string `json:"refund_id,omitempty"`
OutRefundNo *string `json:"out_refund_no,omitempty"`
RefundStatus *string `json:"refund_status,omitempty"`
SuccessTime *string `json:"success_time,omitempty"`
UserReceivedAccount string `json:"user_received_account,omitempty"`
Amount *RefundNotifyAmountData `json:"amount"`
}
type RefundNotifyAmountData struct {
Total *int64 `json:"total,omitempty"`
Refund *int64 `json:"refund,omitempty"`
PayerTotal *int64 `json:"payer_total,omitempty"`
PayerRefund *int64 `json:"payer_refund,omitempty"`
}
func RefundBack(mchID string, request *http.Request) bool {
handler := GetHandler(mchID)
refund := new(RefundNotifyData)
notifyReq, err := handler.ParseNotifyRequest(context.Background(), request, refund)
if err != nil {
exception.ThrowsErr(err)
return false
}
if notifyReq != nil {
if *refund.RefundStatus == "SUCCESS" {
refundBackBusiness(*refund.OutRefundNo)
}
}
return false
}
/**
退款回调处理业务
注意:
同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。 推荐的做法是,当商户系统收到通知进行处理时,先检查对应业务数据的状态,并判断该通知是否已经处理。如果未处理,则再进行处理;如果已处理,则直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。
*/
func refundBackBusiness(outRefundNo string) {
}
/** Refund
退款
*/
func Refund(params params.WechatPartnerRefundParams) {
wechatPayConfig, err := dao.FindWechatPayConfigByPrimaryKey(*params.AreaServiceId)
if wechatPayConfig.AreaServiceId == nil {
exception.ThrowsErrS("[" + strconv.Itoa(int(*params.AreaServiceId)) + "]对应微信支付配不存在")
}
wechatClient := BuildClient(*wechatPayConfig.SpMchid)
svc := refunddomestic.RefundsApiService{Client: wechatClient.Client}
resp, _, err := svc.Create(context.Background(), refunddomestic.CreateRequest{
SubMchid: wechatPayConfig.SubMchid,
OutTradeNo: params.OutTradeNo,
OutRefundNo: params.OutRefundNo,
NotifyUrl: core.String(MainHost + "/wechat/partner/refund/notify/" + *wechatPayConfig.SpMchid),
Amount: &refunddomestic.AmountReq{
Refund: ToWxMoney(*params.RefundAmount),
Total: ToWxMoney(*params.OrderAmount),
Currency: core.String("CNY"),
},
})
if err != nil {
var apiErr = err.(*core.APIError)
exception.ThrowsErrS(apiErr.Message)
}
if *resp.Status != refunddomestic.STATUS_SUCCESS &&
*resp.Status != refunddomestic.STATUS_PROCESSING {
exception.ThrowsErrS("退款错误")
}
}
func JsCode2session(code string, appId string) (responseData response.WxOAuth2ResponseData, err error) {
//var NAME = "微信授权"
//var name = NAME + "-code2Session"
var url = "https://web.weixin.qq.com/sns/jscode2session?appid=${appId}&secret=${secret}&js_code=${code}&grant_type=authorization_code"
baseWechatPayConfig, err := dao.FindWechatAppConfigByPrimaryKey(appId)
url = strings.ReplaceAll(url, "${appId}", *baseWechatPayConfig.AppId)
url = strings.ReplaceAll(url, "${secret}", *baseWechatPayConfig.Secret)
url = strings.ReplaceAll(url, "${code}", code)
//logger.Log.Debug(name,
// zap.String("url", url))
request, err := http.NewRequest(http.MethodGet, url, nil)
client := http.DefaultClient
response, err := client.Do(request)
if err != nil {
return responseData, err
}
defer func() {
response.Body.Close()
}()
bodyBytes, err := ioutil.ReadAll(response.Body)
//logger.Log.Debug(name,
// zap.String("url", url),
// zap.String("response", string(bodyBytes)))
if err != nil {
return responseData, err
}
if err = json.Unmarshal(bodyBytes, &responseData); err != nil {
return responseData, err
}
if responseData.Errmsg != "" {
exception.ThrowsErrS(responseData.Errmsg)
}
return responseData, nil
}
package util
import (
"encoding/json"
"github.com/gin-gonic/gin"
"math/rand"
"strconv"
)
// 生成: 时间戳 + 设置前缀 + 随即字符串
const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
func FandomString(strlen int) string {
result := make([]byte, strlen)
for i := 0; i < strlen; i++ {
result[i] = alphanum[rand.Intn(len(alphanum))]
}
return string(result)
}
func ParamUin32(c *gin.Context, key string) (uint32, error) {
v := c.Param(key)
i, err := strconv.Atoi(v)
return uint32(i), err
}
// Strval 获取变量的字符串值
// 浮点型 3.0将会转换成字符串3, "3"
// 非数值或字符类型的变量将会被转换成JSON格式字符串
func Strval(value interface{}) string {
// interface 转 string
var key string
if value == nil {
return key
}
switch value.(type) {
case float64:
ft := value.(float64)
key = strconv.FormatFloat(ft, 'f', -1, 64)
case float32:
ft := value.(float32)
key = strconv.FormatFloat(float64(ft), 'f', -1, 64)
case int:
it := value.(int)
key = strconv.Itoa(it)
case uint:
it := value.(uint)
key = strconv.Itoa(int(it))
case int8:
it := value.(int8)
key = strconv.Itoa(int(it))
case uint8:
it := value.(uint8)
key = strconv.Itoa(int(it))
case int16:
it := value.(int16)
key = strconv.Itoa(int(it))
case uint16:
it := value.(uint16)
key = strconv.Itoa(int(it))
case int32:
it := value.(int32)
key = strconv.Itoa(int(it))
case uint32:
it := value.(uint32)
key = strconv.Itoa(int(it))
case int64:
it := value.(int64)
key = strconv.FormatInt(it, 10)
case uint64:
it := value.(uint64)
key = strconv.FormatUint(it, 10)
case string:
key = value.(string)
case []byte:
key = string(value.([]byte))
default:
newValue, _ := json.Marshal(value)
key = string(newValue)
}
return key
}
package response
import (
responseState "dc_golang_server_1/web/response/state"
)
type ServerResponse struct {
Code responseState.State `json:"code,omitempty"` //1000为成功,其他都是失败
Msg string `json:"msg,omitempty"` //非1000情况下的错误描述
}
type ServerDataResponse struct {
ServerResponse
Data interface{} `json:"data,omitempty"`
}
func Success(v interface{}) ServerDataResponse {
return ServerDataResponse{ServerResponse: ServerResponse{Code: responseState.StateOK, Msg: responseState.StateMapping[responseState.StateOK]}, Data: v}
}
func Fail(code responseState.State) ServerResponse {
return ServerResponse{Code: code, Msg: responseState.StateMapping[code]}
}
func FailAndMsg(msg string) ServerResponse {
return ServerResponse{Code: responseState.StateBusinessError, Msg: msg}
}
...@@ -8,6 +8,7 @@ const ( ...@@ -8,6 +8,7 @@ const (
StateParamsError StateParamsError
StateSignatureMismatch StateSignatureMismatch
StateBusinessError StateBusinessError
StateAlreadyDel
) )
type StateMap map[State]string type StateMap map[State]string
...@@ -18,4 +19,5 @@ var StateMapping = StateMap{ ...@@ -18,4 +19,5 @@ var StateMapping = StateMap{
StateParamsError: "params error", StateParamsError: "params error",
StateSignatureMismatch: "Signature mismatch", StateSignatureMismatch: "Signature mismatch",
StateBusinessError: "business error, please checking", StateBusinessError: "business error, please checking",
StateAlreadyDel: "info is deleted",
} }
package v1 package v1
import ( import (
"dc_golang_server_1/exception/catch"
"fmt" "fmt"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"log"
"net/http" "net/http"
wechatService "dc_golang_server_1/service/wechat"
) )
//InitWechatPartnerRouter 在InitRouter中添加InitWechatPayConfigRout(r),进行路由初始化
func InitWechatPartnerRouter(engine *gin.Engine) {
engine.POST("/wechat/partner/pay/notify/:mchID", payNotify)
engine.POST("/wechat/partner/refund/notify/:mchID", refundNotify)
}
// WeLogin 微信登录 // WeLogin 微信登录
// @Tags 家长微信 // @Tags 家长微信
// @Summary 登录 // @Summary 登录
...@@ -25,3 +35,29 @@ func WeLogin(c *gin.Context) { ...@@ -25,3 +35,29 @@ func WeLogin(c *gin.Context) {
"token": "diohafuahfuihaeuifhrgjkngk3134", "token": "diohafuahfuihaeuifhrgjkngk3134",
}) })
} }
func payNotify(c *gin.Context) {
defer catch.ExceptionCatch(c)
mchID := c.Param("mchID")
log.Println("微信支付回调通知处理开始:" + mchID)
success := wechatService.PayBack(mchID, c.Request)
if success {
c.JSON(http.StatusOK, map[string]string{
"code": "SUCCESS",
"message": "成功",
})
}
}
func refundNotify(c *gin.Context) {
defer catch.ExceptionCatch(c)
mchID := c.Param("mchID")
log.Println("微信退款回调通知处理开始:" + mchID)
success := wechatService.RefundBack(mchID, c.Request)
if success {
c.JSON(http.StatusOK, map[string]string{
"code": "SUCCESS",
"message": "成功",
})
}
}
package cache
import (
"dc_golang_server_1/logger"
"dc_golang_server_1/wechat/model"
"github.com/goinggo/mapstructure"
"sync"
)
var UWeCustomerMap sync.Map //
func FindUWeCustomerByUnionid(unionid string) (customer model.UWeCustomer, err error) {
customerCache, ok := UWeCustomerMap.Load(unionid)
if ok {
logger.Log.Debug("UWeCustomerMap缓存命中:" + unionid)
} else {
logger.Log.Debug("UWeCustomerMap缓存未命中:" + unionid)
}
if err = mapstructure.Decode(customerCache, &customer); err != nil {
logger.Log.Error(err.Error())
}
return customer, err
}
func SetUWeCustomerByUnionid(customer model.UWeCustomer) {
UWeCustomerMap.Store(customer.Unionid, customer)
}
package dao
import (
"dc_golang_server_1/wechat/model"
)
func FindUWeCustomerByUnionid(unionid string) model.UWeCustomer {
var customer model.UWeCustomer
PgDb.First(&customer, "unionid = ?", unionid)
return customer
}
func NewCustomer(customer model.UWeCustomer) model.UWeCustomer {
PgDb.Create(&customer)
return customer
}
package model
import "time"
type UWeCustomer struct {
Id uint32 `json:"id"`
Openid string `json:"openid"` //微信小程序对应的openid
Unionid string `json:"unionid"` //微信小程序对应的unionid
Nickname string `json:"nickname"` //微信昵称
Avatarurl string `json:"avatarurl"` //微信头像
Country string `json:"country"`
Province string `json:"province"`
Language string `json:"language"`
CreateAt time.Time `json:"createAt"`
LoginAt time.Time `json:"loginAt"`
TestRole bool `json:"testRole"` //是否是有测试权限
updaterId uint32
}
package service
import (
"dc_golang_server_1/wechat/cache"
"dc_golang_server_1/wechat/dao"
"dc_golang_server_1/wechat/model"
"dc_golang_server_1/wechat/web/response"
responseState "dc_golang_server_1/wechat/web/response/state"
)
func FindUWeCustomerByUnionid(unionid string) model.UWeCustomer {
customer, _ := cache.FindUWeCustomerByUnionid(unionid)
if customer == (model.UWeCustomer{}) {
customer = dao.FindUWeCustomerByUnionid(unionid)
cache.SetUWeCustomerByUnionid(customer)
}
return customer
}
func initUWeCustomer(unionid string, openid string) {
var customer model.UWeCustomer
customer.Unionid = unionid
customer.Openid = openid
dao.NewCustomer(customer)
}
type LoginFromWechatAppletData struct {
token string
tokenCreatTime uint32
testRole uint8 // 测试用户(可看到测试校区)
nickname string //微信昵称
masterCardUserID []uint32 //自己创建的学生用户
slaveCardUserID []uint32 //被邀请查看的学生用户
}
//微信小程序登陆
func LoginFromWechatApplet(code string) (LoginFromWechatAppletData, response.ServerErrorResponse) {
var loginFromWechatAppletData LoginFromWechatAppletData
wxOAuth2ResponseData, err := JsCode2session(code)
if err != nil {
return loginFromWechatAppletData, response.ServerErrorResponse{Code: responseState.StateBusinessError, Msg: err.Error()}
} else {
var customer model.UWeCustomer
customer = FindUWeCustomerByUnionid(wxOAuth2ResponseData.Unionid)
if customer == (model.UWeCustomer{}) {
initUWeCustomer(wxOAuth2ResponseData.Unionid, wxOAuth2ResponseData.Openid)
}
}
return loginFromWechatAppletData, response.ServerErrorResponse{}
}
package service
import (
"bytes"
"crypto"
"crypto/aes"
"crypto/cipher"
cryptoRand "crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"dc_golang_server_1/logger"
"encoding/base64"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"go.uber.org/zap"
"io/ioutil"
"math/rand"
"net/http"
"net/url"
"os"
"strings"
"sync"
"time"
)
const appId = "wxff0ab56070f7f6c4" // 小程序或者公众号的appid
const mchId = "1607746840" // 微信支付的商户id
const privateSerialNo = "629D0627491C0FB9513BAFB1D22EDB60A10F4ABE" // 私钥证书号
const aesKey = "TIOROVNsdkjvkdsfjklLljF3012001sd" // 微信支付aes key
const privateKey = "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC7TaGNwUXSqTje" +
"C2XclKbajbQ7JefJY1Ge1dJxZI3VPFaxMSI6+L+ba5eq8+jBD1cTFO1Pti7sI6Rp" +
"2vUanJzPdJwLb42R7hIwzw5YgN3lZ8F/k8HZtzMQGeLhx5IeL2xVUU/y5IbRr1N5" +
"078IqU4Gx7c8/QwzQaR+CnJ0edzn6burSkvy5G1CYzK9XkaqXBXFWMZrLAXLMuAi" +
"71Apqvki5rOAxCCdecpggXnkqv4Ed5ONGeWPxPz1rUUZDo8HdLyznzLjVLMup/gK" +
"h76kF3QW53HPRuKAzHiHMsi3FBEbrGFb9tkYds3Pg/7Eg6ZF8PnOvENO93zpcsjs" +
"HeP5PCKLAgMBAAECggEATHoDF8UthDlSHd+bhLgxdJVdXLU6cdEat3boySHcg49y" +
"ekDskBWW7pe8wrF6fLGNxR3LU7u4ZvSaohoj7+y4XkBnooe2zznctY8ldV+QuqVl" +
"rXigMwwJTm6zY6q/75E/c4qS0UMcBSvIMrsRijDOyhfrqnxL6fXIeyo613IWj539" +
"2gX7UH9LdlN/QPQ2UGWmW5WdoMtxD/q/yCxjHgwqzvL+O3RKPQt2WTXu9Muw02gX" +
"iovtoAJrBrHPa0H1BpHpKDKzfXgpUW5nJL1758zWPe6bLMLfP2PgacnfitdPgILV" +
"82PXghy6D6YjgqtcAq1IM8e6p70HOfhzGiMT8jrhMQKBgQDl3sdclJJ8oOlMqkdJ" +
"/bb5jhMStaoQfUipSjqoCMBDbjbQrVRKWgYaXZIguBN/dGdOtYExDcWmsZjQgx/K" +
"Z8dC8/H0rQxbgfp8HLR99ud7634U5cuZSguJMUe/kkeP5OLmtexGLb8NaPEWwjqv" +
"9YaVaJE+yHIaokk5lqwfHsFGaQKBgQDQmCdPUdHltYJ0nd86jA9/B6h/P/pZYSCC" +
"L7b9AHGzJAmt0jeFDA2RJG0G2pQNzn9tS1xbCfsFoCwVokBQOmTMdA1MI8iZGMHg" +
"C4PItIcTTa57iqnxjNNc0ndtKwwj62gJOyv2mDX3NxxVOeh201rHuzpPmhG2DhvN" +
"Uk4ESdcK0wKBgQCoLx1SQWoEQX8wlo1eN4G+iRKvZ7csJV4abrMH6o8xRtxYRdkc" +
"JU290KRvx/6MDFDhp68Egv0P/3S9apLB9rg15mIrEMmv0iPA1we/+fxYEG7JP3B9" +
"kU1rbWD/azlssJzLDGP43NOiLhvm+OkTgOKdQkqy0tmZXw+QUQtSIaBrsQKBgEDY" +
"7G+WSiCVewbyBXHYFQEyykk1BIlAzrBqUQL6Xl6It5GYJrZv9s/GeTaGQTzBk4/Q" +
"u9d5lNY84zeeh0vpu2hsM64V+cjOj4ctTTzaEpwHrFSv2QFE/HiA3avMlCuehWtJ" +
"P/ObgjtmoKJGLGS0dCZfqFMS3KqyXCZKjgJ7SiUNAoGBAI4QlFzt2LVuAD8+L6u2" +
"newATfnFZXISIqI7x256bsbmhuPtTVy3lGKskxsKfuL7ey75XA9pmLf3nb1LGL2T" +
"t34nCiCpMp0b3PLcEPhPW1XKH+OozYBjMpPu9MHNR1ZPd6Na/Ey3sJIx2wf3oNlw" +
"jouLfNPUPBm0JxmNmqukhHV8"
// 对消息的散列值进行数字签名
func signPKCS1v15(msg, privateKey []byte, hashType crypto.Hash) ([]byte, error) {
block, _ := pem.Decode(privateKey)
if block == nil {
return nil, errors.New("private key decode error")
}
pri, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return nil, errors.New("parse private key error")
}
key, ok := pri.(*rsa.PrivateKey)
if ok == false {
return nil, errors.New("private key format error")
}
sign, err := rsa.SignPKCS1v15(cryptoRand.Reader, key, hashType, msg)
if err != nil {
return nil, errors.New("sign error")
}
return sign, nil
}
// base编码
func base64EncodeStr(src []byte) string {
return base64.StdEncoding.EncodeToString(src)
}
// 生成: 时间戳 + 设置前缀 + 随即字符串
const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
func randomString(strlen int) string {
result := make([]byte, strlen)
for i := 0; i < strlen; i++ {
result[i] = alphanum[rand.Intn(len(alphanum))]
}
return string(result)
}
func hash256(message string) []byte {
h := sha256.New()
h.Write([]byte(message))
return h.Sum(nil)
}
func authorization(method string, paramMap map[string]interface{}, rawUrl string) (token string, err error) {
var body string
if len(paramMap) != 0 {
paramJsonBytes, err := json.Marshal(paramMap)
if err != nil {
return token, err
}
body = string(paramJsonBytes)
}
urlPart, err := url.Parse(rawUrl)
if err != nil {
return token, err
}
canonicalUrl := urlPart.RequestURI()
timestamp := time.Now().Unix()
nonce := randomString(32)
message := fmt.Sprintf("%s\n%s\n%d\n%s\n%s\n", method, canonicalUrl, timestamp, nonce, body)
if err != nil {
return token, err
}
if err != nil {
return token, err
}
signBytes, err := signPKCS1v15(hash256(message), []byte(privateKey), crypto.SHA256)
if err != nil {
return token, err
}
sign := base64EncodeStr(signBytes)
token = fmt.Sprintf("mchid=\"%s\",nonce_str=\"%s\",timestamp=\"%d\",serial_no=\"%s\",signature=\"%s\"",
mchId, nonce, timestamp, privateSerialNo, sign)
return token, nil
}
// 获取公钥
const publicKeyUrl = "https://web.mch.weixin.qq.com/v3/certificates"
type TokenResponse struct {
Data []TokenResponseData `json:"data"`
}
type TokenResponseData struct {
EffectiveTime string `json:"effective_time"`
EncryptCertificate EncryptCertificate `json:"encrypt_certificate"`
ExpireTime string `json:"expire_time"`
SerialNo string `json:"serial_no"`
}
type EncryptCertificate struct {
Algorithm string `json:"algorithm"`
AssociatedData string `json:"associated_data"`
Ciphertext string `json:"ciphertext"`
Nonce string `json:"nonce"`
}
var publicSyncMap sync.Map
// 获取公钥
func getPublicKey() (key string, err error) {
var prepareTime int64 = 24 * 3600 * 3 // 证书提前三天过期旧证书,获取新证书
nowTime := time.Now().Unix()
// 读取公钥缓存数据
cacheValueKey := fmt.Sprintf("app_id:%s:public_key:value", appId)
cacheExpireTimeKey := fmt.Sprintf("app_id:%s:public_key:expire_time", appId)
cacheValue, keyValueOk := publicSyncMap.Load(cacheValueKey)
cacheExpireTime, expireTimeOk := publicSyncMap.Load(cacheExpireTimeKey)
if keyValueOk && expireTimeOk {
// 格式化时间
local, _ := time.LoadLocation("Local")
location, _ := time.ParseInLocation(time.RFC3339, cacheExpireTime.(string), local)
// 判断是否过期,证书没有过期直接返回
if location.Unix()-prepareTime > nowTime {
return cacheValue.(string), nil
}
}
token, err := authorization(http.MethodGet, nil, publicKeyUrl)
if err != nil {
return key, err
}
request, err := http.NewRequest(http.MethodGet, publicKeyUrl, nil)
if err != nil {
return key, err
}
request.Header.Add("Authorization", "WECHATPAY2-SHA256-RSA2048 "+token)
request.Header.Add("User-Agent", "用户代理(https://zh.wikipedia.org/wiki/User_agent)")
request.Header.Add("Content-type", "application/json;charset='utf-8'")
request.Header.Add("Accept", "application/json")
client := http.DefaultClient
response, err := client.Do(request)
if err != nil {
return key, err
}
defer response.Body.Close()
bodyBytes, err := ioutil.ReadAll(response.Body)
if err != nil {
return key, err
}
//fmt.Println(string(bodyBytes))
var tokenResponse TokenResponse
if err = json.Unmarshal(bodyBytes, &tokenResponse); err != nil {
return key, err
}
for _, encryptCertificate := range tokenResponse.Data {
// 格式化时间
local, _ := time.LoadLocation("Local")
location, err := time.ParseInLocation(time.RFC3339, encryptCertificate.ExpireTime, local)
if err != nil {
return key, err
}
// 判断是否过期,证书没有过期直接返回
if location.Unix()-prepareTime > nowTime {
decryptBytes, err := decryptGCM(aesKey, encryptCertificate.EncryptCertificate.Nonce, encryptCertificate.EncryptCertificate.Ciphertext,
encryptCertificate.EncryptCertificate.AssociatedData)
if err != nil {
return key, err
}
key = string(decryptBytes)
publicSyncMap.Store(cacheValueKey, key)
publicSyncMap.Store(cacheExpireTimeKey, encryptCertificate.ExpireTime)
return key, nil
}
}
return key, errors.New("get public key error")
}
func decryptGCM(aesKey, nonceV, ciphertextV, additionalDataV string) ([]byte, error) {
key := []byte(aesKey)
nonce := []byte(nonceV)
additionalData := []byte(additionalDataV)
ciphertext, err := base64.StdEncoding.DecodeString(ciphertextV)
if err != nil {
return nil, err
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
aesGCM, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
plaintext, err := aesGCM.Open(nil, nonce, ciphertext, additionalData)
if err != nil {
return nil, err
}
return plaintext, err
}
func jsApi(payResMap map[string]string) (payJson string, err error) {
payMap := make(map[string]string)
timeStamp := time.Now().Unix()
nonce := randomString(32)
packageStr := "prepay_id=" + payResMap["prepay_id"]
payMap["appId"] = appId
payMap["timeStamp"] = fmt.Sprintf("%v", timeStamp)
payMap["nonceStr"] = nonce
payMap["package"] = packageStr
// 签名
message := fmt.Sprintf("%s\n%s\n%s\n%s\n", appId, fmt.Sprintf("%v", timeStamp), nonce, packageStr)
open, err := os.Open("/Users/apple/data/www/go/work/src/study/testwechantpay/private.pem")
if err != nil {
return payJson, err
}
defer open.Close()
privateKey, err := ioutil.ReadAll(open)
if err != nil {
return payJson, err
}
signBytes, err := signPKCS1v15(hash256(message), privateKey, crypto.SHA256)
if err != nil {
return payJson, err
}
sign := base64EncodeStr(signBytes)
payMap["signType"] = sign
payMap["paySign"] = "RSA"
payJsonBytes, err := json.Marshal(payMap)
if err != nil {
return payJson, err
}
payJson = string(payJsonBytes)
return payJson, nil
}
// 统一下单接口
func IspTransactionsJsapi() (payResMap map[string]string, err error) {
jsapiUrl := "https://web.mch.weixin.qq.com/v3/pay/partner/transactions/jsapi"
payResMap = make(map[string]string)
amount := 10
paramMap := make(map[string]interface{})
paramMap["appid"] = appId
paramMap["mchid"] = mchId
paramMap["description"] = fmt.Sprintf("充值:¥%d", amount)
paramMap["out_trade_no"] = "202109300001"
paramMap["notify_url"] = "http://tools.localhost/notify"
paramMap["amount"] = map[string]interface{}{"total": amount * 100, "currency": "CNY"}
paramMap["payer"] = map[string]string{"openid": "opCO05utXkPQh3Vje13WjEdQpAZ4"}
jsonString, err := json.MarshalIndent(paramMap, "", " ")
logger.Log.Debug("微信jsapi下单开始:" + string(jsonString))
logger.Log.Debug("微信jsapi下单开始",
zap.String("params", string(jsonString)))
token, err := authorization(http.MethodPost, paramMap, jsapiUrl)
if err != nil {
return payResMap, err
}
marshal, _ := json.Marshal(paramMap)
request, err := http.NewRequest(http.MethodPost, jsapiUrl, bytes.NewReader(marshal))
if err != nil {
return payResMap, err
}
request.Header.Add("Authorization", "WECHATPAY2-SHA256-RSA2048 "+token)
request.Header.Add("User-Agent", "用户代理(https://zh.wikipedia.org/wiki/User_agent)")
request.Header.Add("Content-type", "application/json;charset='utf-8'")
request.Header.Add("Accept", "application/json")
client := http.DefaultClient
response, err := client.Do(request)
if err != nil {
return payResMap, err
}
defer func() {
response.Body.Close()
}()
bodyBytes, err := ioutil.ReadAll(response.Body)
logger.Log.Debug("微信jsapi下单结束:" + string(bodyBytes))
if err != nil {
return payResMap, err
}
if err = json.Unmarshal(bodyBytes, &payResMap); err != nil {
return payResMap, err
}
if payResMap["prepay_id"] == "" {
return payResMap, errors.New("code:" + payResMap["code"] + "err:" + payResMap["message"])
}
return payResMap, nil
}
type WxOAuth2ResponseData struct {
Appid string
Openid string
Unionid string
Errcode string
Errmsg string
}
type BaseWechatPayConfig struct {
appId string
secret string
}
//获取微信小程序配置信息
func getBaseWechatPayConfig(appId string) (baseWechatPayConfig BaseWechatPayConfig) {
baseWechatPayConfig.appId = appId
baseWechatPayConfig.secret = "bde10ef69cd62c3394c7f86b637e0619"
return baseWechatPayConfig
}
func JsCode2session(code string) (responseData WxOAuth2ResponseData, err error) {
var NAME = "微信授权"
var name = NAME + "-code2Session"
var url = "https://web.weixin.qq.com/sns/jscode2session?appid=${appId}&secret=${secret}&js_code=${code}&grant_type=authorization_code"
baseWechatPayConfig := getBaseWechatPayConfig(appId)
url = strings.ReplaceAll(url, "${appId}", baseWechatPayConfig.appId)
url = strings.ReplaceAll(url, "${secret}", baseWechatPayConfig.secret)
url = strings.ReplaceAll(url, "${code}", code)
logger.Log.Debug(name,
zap.String("url", url))
request, err := http.NewRequest(http.MethodGet, url, nil)
client := http.DefaultClient
response, err := client.Do(request)
if err != nil {
return responseData, err
}
defer func() {
response.Body.Close()
}()
bodyBytes, err := ioutil.ReadAll(response.Body)
logger.Log.Debug(name,
zap.String("url", url),
zap.String("response", string(bodyBytes)))
if err != nil {
return responseData, err
}
if err = json.Unmarshal(bodyBytes, &responseData); err != nil {
return responseData, err
}
if responseData.Errmsg != "" {
return responseData, errors.New(responseData.Errmsg)
}
return responseData, nil
}
package response
import (
responseState "dc_golang_server_1/wechat/web/response/state"
)
type ServerErrorResponse struct {
Code responseState.State `json:"code"` //1000为成功,其他都是失败
Msg string `json:"msg"`
}
type ServerDataResponse struct {
Code responseState.State `json:"code"` //1000为成功,其他都是失败
Data interface{} `json:"data"`
}
package web
import "github.com/gin-gonic/gin"
// InitWechatRout 在InitRouter中添加InitWechatRout(r),进行路由初始化
func InitWechatRout(engine *gin.Engine) {
engine.GET("/v1/test", test)
engine.POST("/v1/login", weLogin)
}
package web
import (
"dc_golang_server_1/wechat/model"
wechatService "dc_golang_server_1/wechat/service"
"dc_golang_server_1/wechat/web/response"
responseState "dc_golang_server_1/wechat/web/response/state"
"github.com/gin-gonic/gin"
"net/http"
)
type TestResponse struct {
Code responseState.State `json:"code"` //1000为成功,其他都是失败
Msg string `json:"msg"` //code非1000时的错误描述
Data model.UWeCustomer `json:"data"`
}
// @Tags 微信
// @Summary 测试
// @Description 测试使用
// @Param unionid query string true "用户微信的unionid"
// @Product json
// @Success 200 {object} TestResponse"
// @Router /v1/test [GET]
func test(c *gin.Context) {
unionid := c.Query("unionid")
var customer model.UWeCustomer
customer = wechatService.FindUWeCustomerByUnionid(unionid)
if customer.Id == 0 {
c.JSON(http.StatusOK, response.ServerErrorResponse{Code: responseState.StateBusinessError, Msg: "用户不存在"})
} else {
c.JSON(http.StatusOK, response.ServerDataResponse{Code: responseState.StateOK, Data: customer})
}
}
// @Tags 微信
// @Summary 登录
// @Description 微信code传至后台,后台返回token,token有效时长:24小时
// @Param code path string true "用户微信的code"
// @Product json
// @Success 200 {object} service.LoginFromWechatAppletResponse"
// @Router /v1/login/{code} [POST]
func weLogin(c *gin.Context) {
code := c.Param("code")
responseData, err := wechatService.LoginFromWechatApplet(code)
if err != (response.ServerErrorResponse{}) {
c.JSON(http.StatusOK, err)
} else {
c.JSON(http.StatusOK, responseData)
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment