新增微信支付对接,后台客服取消订单

This commit is contained in:
wucongxing 2023-08-03 14:42:33 +08:00
parent faabe27f32
commit 96308ff608
18 changed files with 684 additions and 1 deletions

View File

@ -6,6 +6,7 @@ type Api struct {
userDoctorManage // 医生管理
Admin // 公共方法
basic // 基础数据
order // 订单管理
}
// SysSetting 系统设置
@ -31,3 +32,8 @@ type basic struct {
Bank // 银行管理
Area // 省市区管理
}
// 订单管理
type order struct {
OrderInquiry // 问诊订单
}

View File

@ -0,0 +1,51 @@
package controller
import (
"github.com/gin-gonic/gin"
"hospital-admin-api/api/responses"
"hospital-admin-api/api/service"
"strconv"
)
type OrderInquiry struct{}
// GetOrderInquiryPage 获取问诊订单列表-分页
func (r *OrderInquiry) GetOrderInquiryPage(c *gin.Context) {
responses.Ok(c)
}
// GetOrderInquiry 问诊订单详情
func (r *OrderInquiry) GetOrderInquiry(c *gin.Context) {
responses.Ok(c)
}
// DeleteOrderInquiry 删除问诊订单
func (r *OrderInquiry) DeleteOrderInquiry(c *gin.Context) {
responses.Ok(c)
}
// CancelOrderInquiry 取消问诊订单
func (r *OrderInquiry) CancelOrderInquiry(c *gin.Context) {
id := c.Param("order_inquiry_id")
if id == "" {
responses.FailWithMessage("缺少参数", c)
return
}
// 将 id 转换为 int64 类型
orderInquiryId, err := strconv.ParseInt(id, 10, 64)
if err != nil {
responses.Fail(c)
return
}
// 业务处理
orderInquiryService := service.OrderInquiryService{}
_, err = orderInquiryService.CancelOrderInquiry(orderInquiryId)
if err != nil {
responses.FailWithMessage(err.Error(), c)
return
}
responses.Ok(c)
}

73
api/dao/orderInquiry.go Normal file
View File

@ -0,0 +1,73 @@
package dao
import (
"gorm.io/gorm"
"gorm.io/gorm/clause"
"hospital-admin-api/api/model"
"hospital-admin-api/global"
)
type OrderInquiryDao struct {
}
// GetOrderInquiryById 获取问诊订单数据-问诊订单id
func (r *OrderInquiryDao) GetOrderInquiryById(doctorId int64) (m *model.OrderInquiry, err error) {
err = global.Db.First(&m, doctorId).Error
if err != nil {
return nil, err
}
return m, nil
}
// GetOrderInquiryPreloadById 获取问诊订单数据-加载全部关联-问诊订单id
func (r *OrderInquiryDao) GetOrderInquiryPreloadById(orderInquiryId int64) (m *model.OrderInquiry, err error) {
err = global.Db.Preload(clause.Associations).First(&m, orderInquiryId).Error
if err != nil {
return nil, err
}
return m, nil
}
// DeleteOrderInquiry 删除问诊订单
func (r *OrderInquiryDao) DeleteOrderInquiry(tx *gorm.DB, maps interface{}) error {
err := tx.Where(maps).Delete(&model.OrderInquiry{}).Error
if err != nil {
return err
}
return nil
}
// EditOrderInquiry 修改问诊订单
func (r *OrderInquiryDao) EditOrderInquiry(tx *gorm.DB, maps interface{}, data interface{}) error {
err := tx.Model(&model.OrderInquiry{}).Where(maps).Updates(data).Error
if err != nil {
return err
}
return nil
}
// EditOrderInquiryById 修改问诊订单-问诊订单id
func (r *OrderInquiryDao) EditOrderInquiryById(tx *gorm.DB, orderInquiryId int64, data interface{}) error {
err := tx.Model(&model.OrderInquiry{}).Where("order_inquiry_id = ?", orderInquiryId).Updates(data).Error
if err != nil {
return err
}
return nil
}
// GetOrderInquiryList 获取问诊订单列表
func (r *OrderInquiryDao) GetOrderInquiryList(maps interface{}) (m []*model.OrderInquiry, err error) {
err = global.Db.Where(maps).Find(&m).Error
if err != nil {
return nil, err
}
return m, nil
}
// AddOrderInquiry 新增问诊订单
func (r *OrderInquiryDao) AddOrderInquiry(tx *gorm.DB, model *model.OrderInquiry) (*model.OrderInquiry, error) {
if err := tx.Create(model).Error; err != nil {
return nil, err
}
return model, nil
}

View File

@ -0,0 +1,73 @@
package dao
import (
"gorm.io/gorm"
"gorm.io/gorm/clause"
"hospital-admin-api/api/model"
"hospital-admin-api/global"
)
type OrderInquiryRefundDao struct {
}
// GetOrderInquiryRefundById 获取问诊退款订单数据-问诊退款订单id
func (r *OrderInquiryRefundDao) GetOrderInquiryRefundById(doctorId int64) (m *model.OrderInquiryRefund, err error) {
err = global.Db.First(&m, doctorId).Error
if err != nil {
return nil, err
}
return m, nil
}
// GetOrderInquiryRefundPreloadById 获取问诊退款订单数据-加载全部关联-问诊退款订单id
func (r *OrderInquiryRefundDao) GetOrderInquiryRefundPreloadById(inquiryRefundId int64) (m *model.OrderInquiryRefund, err error) {
err = global.Db.Preload(clause.Associations).First(&m, inquiryRefundId).Error
if err != nil {
return nil, err
}
return m, nil
}
// DeleteOrderInquiryRefund 删除问诊退款订单
func (r *OrderInquiryRefundDao) DeleteOrderInquiryRefund(tx *gorm.DB, maps interface{}) error {
err := tx.Where(maps).Delete(&model.OrderInquiryRefund{}).Error
if err != nil {
return err
}
return nil
}
// EditOrderInquiryRefund 修改问诊退款订单
func (r *OrderInquiryRefundDao) EditOrderInquiryRefund(tx *gorm.DB, maps interface{}, data interface{}) error {
err := tx.Model(&model.OrderInquiryRefund{}).Where(maps).Updates(data).Error
if err != nil {
return err
}
return nil
}
// EditOrderInquiryRefundById 修改问诊退款订单-问诊退款订单id
func (r *OrderInquiryRefundDao) EditOrderInquiryRefundById(tx *gorm.DB, inquiryRefundId int64, data interface{}) error {
err := tx.Model(&model.OrderInquiryRefund{}).Where("inquiry_refund_id = ?", inquiryRefundId).Updates(data).Error
if err != nil {
return err
}
return nil
}
// GetOrderInquiryRefundList 获取问诊退款订单列表
func (r *OrderInquiryRefundDao) GetOrderInquiryRefundList(maps interface{}) (m []*model.OrderInquiryRefund, err error) {
err = global.Db.Where(maps).Find(&m).Error
if err != nil {
return nil, err
}
return m, nil
}
// AddOrderInquiryRefund 新增问诊退款订单
func (r *OrderInquiryRefundDao) AddOrderInquiryRefund(tx *gorm.DB, model *model.OrderInquiryRefund) (*model.OrderInquiryRefund, error) {
if err := tx.Create(model).Error; err != nil {
return nil, err
}
return model, nil
}

62
api/model/orderInquiry.go Normal file
View File

@ -0,0 +1,62 @@
package model
import (
"gorm.io/gorm"
"hospital-admin-api/global"
"time"
)
// OrderInquiry 订单-问诊表
type OrderInquiry struct {
OrderInquiryId int64 `gorm:"column:order_inquiry_id;type:bigint(19);primary_key;comment:主键id" json:"order_inquiry_id"`
UserId int64 `gorm:"column:user_id;type:bigint(19);comment:用户id-患者;NOT NULL" json:"user_id"`
PatientId int64 `gorm:"column:patient_id;type:bigint(19);comment:患者id;NOT NULL" json:"patient_id"`
DoctorId int64 `gorm:"column:doctor_id;type:bigint(19);comment:医生id未分配时为null" json:"doctor_id"`
FamilyId int64 `gorm:"column:family_id;type:bigint(19);comment:家庭成员id就诊用户;NOT NULL" json:"family_id"`
InquiryType int `gorm:"column:inquiry_type;type:tinyint(1);comment:订单类型1:专家问诊 2:快速问诊 3:公益问诊 4:问诊购药 5:检测);NOT NULL" json:"inquiry_type"`
InquiryMode int `gorm:"column:inquiry_mode;type:tinyint(1);comment:订单问诊方式1:图文 2:视频 3:语音 4:电话 5:会员);NOT NULL" json:"inquiry_mode"`
InquiryStatus int `gorm:"column:inquiry_status;type:tinyint(1);default:1;comment:问诊订单状态1:待支付 2:待分配 3:待接诊 4:已接诊 5:已完成 6:已结束 7:已取消);NOT NULL" json:"inquiry_status"`
IsDelete int `gorm:"column:is_delete;type:tinyint(1);default:0;comment:删除状态0:否 1:是)" json:"is_delete"`
InquiryRefundStatus int `gorm:"column:inquiry_refund_status;type:tinyint(1);default:0;comment:问诊订单退款状态0:无退款 1:申请退款 2:退款中 3:退款成功 4:拒绝退款 5:退款关闭 6:退款异常)" json:"inquiry_refund_status"`
InquiryPayChannel int `gorm:"column:inquiry_pay_channel;type:tinyint(1);comment:支付渠道1:小程序支付 2:微信扫码支付 3:模拟支付)" json:"inquiry_pay_channel"`
InquiryPayStatus int `gorm:"column:inquiry_pay_status;type:tinyint(1);default:1;comment:支付状态1:未支付 2:已支付 3:支付中 4:支付失败 5:支付超时 6:支付关闭 7:已撤销 8:转入退款);NOT NULL" json:"inquiry_pay_status"`
InquiryNo string `gorm:"column:inquiry_no;type:varchar(30);comment:系统订单编号;NOT NULL" json:"inquiry_no"`
EscrowTradeNo string `gorm:"column:escrow_trade_no;type:varchar(100);comment:第三方支付流水号" json:"escrow_trade_no"`
AmountTotal float64 `gorm:"column:amount_total;type:decimal(10,2);default:0.00;comment:订单金额" json:"amount_total"`
CouponAmountTotal float64 `gorm:"column:coupon_amount_total;type:decimal(10,2);comment:优惠卷总金额" json:"coupon_amount_total"`
PaymentAmountTotal float64 `gorm:"column:payment_amount_total;type:decimal(10,2);default:0.00;comment:实际付款金额" json:"payment_amount_total"`
PayTime time.Time `gorm:"column:pay_time;type:datetime;comment:支付时间" json:"pay_time"`
ReceptionTime time.Time `gorm:"column:reception_time;type:datetime;comment:接诊时间(已接诊)" json:"reception_time"`
CompleteTime time.Time `gorm:"column:complete_time;type:datetime;comment:订单完成时间(问诊完成时间)" json:"complete_time"`
FinishTime time.Time `gorm:"column:finish_time;type:datetime;comment:订单结束时间" json:"finish_time"`
StatisticsStatus int `gorm:"column:statistics_status;type:tinyint(1);default:0;comment:订单统计状态0:未统计 1:已统计 2:统计失败)" json:"statistics_status"`
StatisticsTime time.Time `gorm:"column:statistics_time;type:datetime;comment:订单统计时间" json:"statistics_time"`
IsWithdrawal int `gorm:"column:is_withdrawal;type:tinyint(1);default:0;comment:是否提现0:否 1:是 2:提现中)" json:"is_withdrawal"`
WithdrawalTime time.Time `gorm:"column:withdrawal_time;type:datetime;comment:提现时间" json:"withdrawal_time"`
CancelTime time.Time `gorm:"column:cancel_time;type:datetime;comment:订单取消时间" json:"cancel_time"`
CancelReason int `gorm:"column:cancel_reason;type:tinyint(1);comment:取消订单原因1:医生未接诊 2:主动取消 3:无可分配医生 4:客服取消 5:支付超时)" json:"cancel_reason"`
CancelRemarks string `gorm:"column:cancel_remarks;type:varchar(255);comment:取消订单备注(自动添加)" json:"cancel_remarks"`
PatientName string `gorm:"column:patient_name;type:varchar(255);comment:患者姓名-就诊人" json:"patient_name"`
PatientNameMask string `gorm:"column:patient_name_mask;type:varchar(255);comment:患者姓名-就诊人(掩码)" json:"patient_name_mask"`
PatientSex int `gorm:"column:patient_sex;type:tinyint(1);default:0;comment:患者性别-就诊人0:未知 1:男 2:女)" json:"patient_sex"`
PatientAge int `gorm:"column:patient_age;type:int(1);comment:患者年龄-就诊人" json:"patient_age"`
Model
}
func (m *OrderInquiry) TableName() string {
return "gdxz_order_inquiry"
}
func (m *OrderInquiry) BeforeCreate(tx *gorm.DB) error {
if m.OrderInquiryId == 0 {
m.OrderInquiryId = global.Snowflake.Generate().Int64()
}
m.CreatedAt = LocalTime(time.Now())
tx.Statement.SetColumn("CreatedAt", m.CreatedAt)
m.UpdatedAt = LocalTime(time.Now())
tx.Statement.SetColumn("UpdatedAt", m.UpdatedAt)
return nil
}

View File

@ -0,0 +1,39 @@
package model
import (
"gorm.io/gorm"
"hospital-admin-api/global"
"time"
)
// OrderInquiryRefund 订单-问诊-退款表
type OrderInquiryRefund struct {
InquiryRefundId int64 `gorm:"column:inquiry_refund_id;type:bigint(19);primary_key;comment:主键id" json:"inquiry_refund_id"`
PatientId int64 `gorm:"column:patient_id;type:bigint(19);comment:患者id" json:"patient_id"`
OrderInquiryId int64 `gorm:"column:order_inquiry_id;type:bigint(19);comment:订单-问诊id" json:"order_inquiry_id"`
InquiryNo string `gorm:"column:inquiry_no;type:varchar(40);comment:系统订单编号" json:"inquiry_no"`
InquiryRefundNo string `gorm:"column:inquiry_refund_no;type:varchar(50);comment:系统退款编号" json:"inquiry_refund_no"`
RefundId string `gorm:"column:refund_id;type:varchar(50);comment:第三方退款单号" json:"refund_id"`
InquiryRefundStatus int `gorm:"column:inquiry_refund_status;type:tinyint(4);comment:问诊订单退款状态0:无退款 1:申请退款 2:退款中 3:退款成功 4:拒绝退款 5:退款关闭 6:退款异常)" json:"inquiry_refund_status"`
RefundTotal float64 `gorm:"column:refund_total;type:decimal(10,2);comment:退款金额" json:"refund_total"`
RefundReason string `gorm:"column:refund_reason;type:varchar(255);comment:退款原因" json:"refund_reason"`
SuccessTime time.Time `gorm:"column:success_time;type:datetime;comment:退款成功时间" json:"success_time"`
Model
}
func (m *OrderInquiryRefund) TableName() string {
return "gdxz_order_inquiry_refund"
}
func (m *OrderInquiryRefund) BeforeCreate(tx *gorm.DB) error {
if m.InquiryRefundId == 0 {
m.InquiryRefundId = global.Snowflake.Generate().Int64()
}
m.CreatedAt = LocalTime(time.Now())
tx.Statement.SetColumn("CreatedAt", m.CreatedAt)
m.UpdatedAt = LocalTime(time.Now())
tx.Statement.SetColumn("UpdatedAt", m.UpdatedAt)
return nil
}

View File

@ -355,4 +355,23 @@ func privateRouter(r *gin.Engine, api controller.Api) {
}
}
// 订单管理
orderGroup := adminGroup.Group("/order")
{
// 问诊订单
inquiryGroup := orderGroup.Group("/inquiry")
{
// 获取问诊订单列表-分页
inquiryGroup.GET("", api.OrderInquiry.GetOrderInquiryPage)
// 问诊订单详情
inquiryGroup.GET("/:order_inquiry_id", api.OrderInquiry.GetOrderInquiry)
// 删除问诊订单
inquiryGroup.DELETE("/:order_inquiry_id", api.OrderInquiry.DeleteOrderInquiry)
// 取消问诊订单
inquiryGroup.PUT("/:order_inquiry_id", api.OrderInquiry.CancelOrderInquiry)
}
}
}

182
api/service/OrderInquiry.go Normal file
View File

@ -0,0 +1,182 @@
package service
import (
"errors"
"hospital-admin-api/api/dao"
"hospital-admin-api/api/model"
"hospital-admin-api/config"
"hospital-admin-api/extend/weChat"
"hospital-admin-api/global"
"strconv"
"time"
)
type OrderInquiryService struct {
}
// CancelOrderInquiry 取消问诊订单
func (r *OrderInquiryService) CancelOrderInquiry(orderInquiryId int64) (bool, error) {
// 获取订单数据
orderInquiryDao := dao.OrderInquiryDao{}
orderInquiry, err := orderInquiryDao.GetOrderInquiryById(orderInquiryId)
if err != nil || orderInquiry == nil {
return false, errors.New("订单数据错误")
}
// 检测订单状态 问诊订单状态1:待支付 2:待分配 3:待接诊 4:已接诊 5:已完成 6:已结束 7:已取消)
if orderInquiry.InquiryStatus == 6 {
return false, errors.New("订单已结束,无法取消")
}
if orderInquiry.InquiryStatus == 7 {
return false, errors.New("订单已取消,无法再次取消")
}
if orderInquiry.InquiryStatus == 1 {
return false, errors.New("订单处于待支付状态,无法取消")
}
if orderInquiry.InquiryStatus == 2 {
return false, errors.New("订单处于分配状态,无法取消")
}
if orderInquiry.InquiryStatus == 3 {
return false, errors.New("订单处于等待接诊状态,无法取消")
}
// 检测订单退款状态 问诊订单退款状态0:无退款 1:申请退款 2:退款中 3:退款成功 4:拒绝退款 5:退款关闭 6:退款异常)
if orderInquiry.InquiryRefundStatus == 1 {
return false, errors.New("订单申请退款中,无法取消")
}
if orderInquiry.InquiryRefundStatus == 2 {
return false, errors.New("订单正在退款中,无法取消")
}
if orderInquiry.InquiryRefundStatus == 3 {
return false, errors.New("订单已退款成功,无法取消")
}
if orderInquiry.InquiryRefundStatus == 6 {
return false, errors.New("订单退款异常,请联系技术人员")
}
// 检测支付状态 支付状态1:未支付 2:已支付 3:支付中 4:支付失败 5:支付超时 6:支付关闭 7:已撤销 8:转入退款)
if orderInquiry.InquiryPayStatus != 2 {
return false, errors.New("订单未支付,无需取消")
}
// 订单完成时间预留5分钟进行操作
if !orderInquiry.CompleteTime.IsZero() {
// 计算三天后的时间
threeDaysLater := orderInquiry.CompleteTime.Add(3 * 24 * time.Hour)
if time.Since(threeDaysLater) > 5*time.Minute {
return false, errors.New("距离订单完成时间不足5分钟无法取消")
}
}
// 开始事务
tx := global.Db.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
// 问诊订单修改数据
orderInquiryData := make(map[string]interface{})
// 发起退款
if orderInquiry.PaymentAmountTotal > 0 {
// 退款编号
inquiryRefundNo := strconv.FormatInt(global.Snowflake.Generate().Int64(), 10)
refundRequest := weChat.RefundRequest{
TransactionId: orderInquiry.EscrowTradeNo,
OutTradeNo: orderInquiry.InquiryNo,
OutRefundNo: inquiryRefundNo,
Reason: "客服取消",
PaymentAmountTotal: int64(orderInquiry.PaymentAmountTotal * 100),
NotifyUrl: "https://dev.hospital.applets.igandanyiyuan.com/" + config.C.Wechat.PatientInquiryRefundNotifyUrl,
}
refund, err := refundRequest.Refund()
if err != nil {
tx.Rollback()
return false, errors.New(err.Error())
}
if refund.Status == nil {
tx.Rollback()
return false, errors.New("退款状态错误")
}
// 退款状态转换
var inquiryRefundStatus int
var successTime time.Time
if *refund.Status == "SUCCESS" {
// 退款成功
inquiryRefundStatus = 3
if refund.SuccessTime != nil {
// 使用 time.Parse 解析时间字符串为 time.Time 类型
successTime = *refund.SuccessTime
}
} else if *refund.Status == "CLOSED" {
// 退款关闭
inquiryRefundStatus = 5
} else if *refund.Status == "PROCESSING" {
// 退款处理中
inquiryRefundStatus = 2
} else if *refund.Status == "ABNORMAL" {
// 退款异常
tx.Rollback()
return false, errors.New("退款状态错误")
} else {
tx.Rollback()
return false, errors.New("退款状态错误")
}
if refund.RefundId == nil {
tx.Rollback()
return false, errors.New("缺少退款订单编号")
}
// 新增退款表
orderInquiryRefundDao := dao.OrderInquiryRefundDao{}
orderInquiryRefund := &model.OrderInquiryRefund{
PatientId: orderInquiry.PatientId,
OrderInquiryId: orderInquiryId,
InquiryNo: orderInquiry.InquiryNo,
InquiryRefundNo: inquiryRefundNo,
RefundId: *refund.RefundId,
InquiryRefundStatus: inquiryRefundStatus,
RefundTotal: orderInquiry.PaymentAmountTotal,
RefundReason: "客服取消",
SuccessTime: successTime,
}
orderInquiryRefund, err = orderInquiryRefundDao.AddOrderInquiryRefund(tx, orderInquiryRefund)
if err != nil || orderInquiryRefund == nil {
tx.Rollback()
return false, errors.New(err.Error())
}
}
orderInquiryData["inquiry_status"] = 7 // 问诊订单状态1:待支付 2:待分配 3:待接诊 4:已接诊 5:已完成 6:已结束 7:已取消)
orderInquiryData["cancel_time"] = time.Now().Format("2006-01-02 15:04:05") // 订单取消时间
orderInquiryData["cancel_reason"] = 4 // 取消订单原因1:医生未接诊 2:主动取消 3:无可分配医生 4:客服取消 5:支付超时)
orderInquiryData["cancel_remarks"] = "客服取消" // 取消订单备注(自动添加)
// 修改问诊订单退款状态
err = orderInquiryDao.EditOrderInquiryById(tx, orderInquiryId, orderInquiryData)
if err != nil {
tx.Rollback()
return false, errors.New("取消订单失败")
}
tx.Commit()
return true, nil
}

View File

@ -56,3 +56,19 @@ im:
dysms:
dysms-access-key: LTAI4GGygjsKhyBwvvC3CghV
dysms-access-secret: rcx7lO9kQxG10m8NqNPEfEtT9IS8EI
# [微信]
wechat:
patient-app-id: wx70a196902e0841b6
patient-app-secret: 2671d2f4285180ddec5a5a2b16ed50f2
patient-inquiry-pay-notify-url: callback/wxpay/inquiry/success
patient-inquiry-refund-notify-url: callback/wxpay/inquiry/refund
patient-product-pay-notify-url: callback/wxpay/product/success
patient-product-refund-notify-url: callback/wxpay/product/refund
patient-detection-pay-notify-url: callback/wxpay/detection/success
patient-detection-refund-notify-url: callback/wxpay/detection/refund
doctor-app-id: wxc83296720404aa7b
doctor-app-secret: 817665d3763637fe66d56548f8484622
mch-id: 1636644248
v3-api-secret: gdxz292sjSOadN3m2pCda03NfCsmNadY
mch-certificate-serial-number: 7DEC0E6C57E0DC71F077F02F52406566AF39BEBB

View File

@ -14,4 +14,5 @@ type Config struct {
CaOnline CaOnline `mapstructure:"ca-online" json:"ca-online" yaml:"ca-online"`
Im Im `mapstructure:"im" json:"im" yaml:"im"`
Dysms Dysms `mapstructure:"dysms" json:"dysms" yaml:"dysms"`
Wechat Wechat `mapstructure:"wechat" json:"wechat" yaml:"wechat"`
}

17
config/wechat.go Normal file
View File

@ -0,0 +1,17 @@
package config
type Wechat struct {
PatientAppId string `mapstructure:"patient-app-id" json:"patient-app-id" yaml:"patient-app-id"`
PatientAppSecret string `mapstructure:"patient-app-secret" json:"patient-app-secret" yaml:"patient-app-secret"`
PatientInquiryPayNotifyUrl string `mapstructure:"patient-inquiry-pay-notify-url" json:"patient-inquiry-pay-notify-url" yaml:"patient-inquiry-pay-notify-url"`
PatientInquiryRefundNotifyUrl string `mapstructure:"patient-inquiry-refund-notify-url" json:"patient-inquiry-refund-notify-url" yaml:"patient-inquiry-refund-notify-url"`
PatientProductPayNotifyUrl string `mapstructure:"patient-product-pay-notify-url" json:"patient-product-pay-notify-url" yaml:"patient-product-pay-notify-url"`
PatientProductRefundNotifyUrl string `mapstructure:"patient-product-refund-notify-url" json:"patient-product-refund-notify-url" yaml:"patient-product-refund-notify-url"`
PatientDetectionPayNotifyUrl string `mapstructure:"patient-detection-pay-notify-url" json:"patient-detection-pay-notify-url" yaml:"patient-detection-pay-notify-url"`
PatientDetectionRefundNotifyUrl string `mapstructure:"patient-detection-refund-notify-url" json:"patient-detection-refund-notify-url" yaml:"patient-detection-refund-notify-url"`
DoctorAppId string `mapstructure:"doctor-app-id" json:"doctor-app-id" yaml:"doctor-app-id"`
DoctorAppSecret string `mapstructure:"doctor-app-secret" json:"doctor-app-secret" yaml:"doctor-app-secret"`
MchId string `mapstructure:"mch-id" json:"mch-id" yaml:"mch-id"` // 商户号
V3ApiSecret string `mapstructure:"v3-api-secret" json:"v3-api-secret" yaml:"v3-api-secret"` // 商户APIv3密钥
MchCertificateSerialNumber string `mapstructure:"mch-certificate-serial-number" json:"mch-certificate-serial-number" yaml:"mch-certificate-serial-number"` // 商户证书序列号
}

Binary file not shown.

View File

@ -0,0 +1,25 @@
-----BEGIN CERTIFICATE-----
MIIELjCCAxagAwIBAgIUfewObFfg3HHwd/AvUkBlZq85vrswDQYJKoZIhvcNAQEL
BQAwXjELMAkGA1UEBhMCQ04xEzARBgNVBAoTClRlbnBheS5jb20xHTAbBgNVBAsT
FFRlbnBheS5jb20gQ0EgQ2VudGVyMRswGQYDVQQDExJUZW5wYXkuY29tIFJvb3Qg
Q0EwHhcNMjMwMzAxMDExNzE3WhcNMjgwMjI4MDExNzE3WjCBhzETMBEGA1UEAwwK
MTYzNjY0NDI0ODEbMBkGA1UECgwS5b6u5L+h5ZWG5oi357O757ufMTMwMQYDVQQL
DCrljJfkuqzmrKPmrKPnm7jnhaflgaXlurfnp5HmioDmnInpmZDlhazlj7gxCzAJ
BgNVBAYMAkNOMREwDwYDVQQHDAhTaGVuWmhlbjCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBAK7x9ywApasrs+xIt0XxHhYOK4xZeuNEj5o7pzTcZMRkd6CB
1eX2ydlUfDe+FcNS03cwLNtUeAfrZXlhA807b4HuReaFqCrbt87hfIF/lOBpePdN
sSr8Wi6OKfPakuLNZ4RCOdlvgxPMhOf2b9VFQwns8h72H6JrFz7xR+Heundy3KGH
kEoG2qcl7nKyhkUVSRSH4/yzsxsDIwkpXjEiSL87E+A6GKik7jphYc+vfV9NURiA
JwbVWbQMhpj3YLRxgXadLS4xMryB59fYKm+VFMNg/jSZG55Jz2DW02aR2h3KjegY
SGA9qMKojkFVOrZvxKWOvtEWw2JQ/1dRvzYpgw0CAwEAAaOBuTCBtjAJBgNVHRME
AjAAMAsGA1UdDwQEAwID+DCBmwYDVR0fBIGTMIGQMIGNoIGKoIGHhoGEaHR0cDov
L2V2Y2EuaXRydXMuY29tLmNuL3B1YmxpYy9pdHJ1c2NybD9DQT0xQkQ0MjIwRTUw
REJDMDRCMDZBRDM5NzU0OTg0NkMwMUMzRThFQkQyJnNnPUhBQ0M0NzFCNjU0MjJF
MTJCMjdBOUQzM0E4N0FEMUNERjU5MjZFMTQwMzcxMA0GCSqGSIb3DQEBCwUAA4IB
AQCBjjBeJTRtnsIISCfoQ7pj70dil5LTBNjpzV9swGOG7sY9dTMbHV0K2BUyRvbD
eY2fuCcr3Qt3L1RykyomKbxD4O6b/ZPLTkiq2m+hq34g2Ig7zUx0a0sNKf/NPAq6
6pVu5/XdIKYXz2OsByBI8aN2hkcUApmM1qlm+gb4FSmTzkdHaFRblhDlKfiRdAw8
T4yliaOFovqhf/S8o5Xa76kQMOb3uJ/oE4KX02kp/ig5wZKj9nay46AyovBXOj+Z
0oaeIPFDXCPzZV7LA4E45zDl43fL8r+9OzaVprRZ9ASO0cgnPsHyS+o/mpMEBOR4
NIwdIaqYRQ/Rh6eZQvrqDZ4r
-----END CERTIFICATE-----

View File

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCu8fcsAKWrK7Ps
SLdF8R4WDiuMWXrjRI+aO6c03GTEZHeggdXl9snZVHw3vhXDUtN3MCzbVHgH62V5
YQPNO2+B7kXmhagq27fO4XyBf5TgaXj3TbEq/Foujinz2pLizWeEQjnZb4MTzITn
9m/VRUMJ7PIe9h+iaxc+8Ufh3rp3ctyhh5BKBtqnJe5ysoZFFUkUh+P8s7MbAyMJ
KV4xIki/OxPgOhiopO46YWHPr31fTVEYgCcG1Vm0DIaY92C0cYF2nS0uMTK8gefX
2CpvlRTDYP40mRueSc9g1tNmkdodyo3oGEhgPajCqI5BVTq2b8Sljr7RFsNiUP9X
Ub82KYMNAgMBAAECggEAT7uNyGs/FkVjykPV67WZ3bl1lZDOljgQLt4TNd9gubWE
ZA3om9efZULBHnKu3oeoQ0EcoJXd4tYhOHHD1szI5HHhP9AYtffPzSUtpqOsCZ9o
d2XcYlgDDgbTDgXHPkEZdcjtLrFJD0P+Ku5BR/U6OZLZQs0v28ltHc2/0iy91WQt
iB/NSf88dY1L5RVZc7dZfb8nFB2/BsITUXDFJpzdim3AXKvNYzDajs/yuoTp5r/b
2EOjUIGPP55W/8iPwbezgDToF79toum6V7yUpCq5P+QQvnnbJfoc1yXB48zHjmgP
I2P1cDVtFs+V9Edb6qsU3e4CMyBQg5Uykk3TUqZRIQKBgQDnXgGk8XnI1kG9jZMR
wMkra87Dx76vrBdM47n+c2I1xubM9WSeSUxtXGyryTwJ9i6/BUyc3Eyb2wi4Q8AK
1ZIEFhvIUkBJpUgevAksj4eX6z0qDwDc3ZhgAAWOplnOCCyJuOBKmZks4GaQb4Gv
/aSFVQ9jefOc5e9RIDNzkHFkaQKBgQDBkigvmusjad95Jt34TwyE4Dr9z2LzBker
6ebcyQmv4YdKvq0KaaVMrroH7pNu7CSFrj4CGqdS/Gg1BtK8Xaxn+gEWT6RPcsi1
mPwy/7oiK0GodzNI6RW5HDZEHKG2icEb4ycDnHeLfThKmP/gckPifjcJHrP5OX1A
V6qrq4iFBQKBgGdgB1gNVJ65rJHnCckq3Dd8advsCXUwbRC7x0S7hSwF/OWi1xwq
H+3VF/EBbsP8rRJIadzESa5xhUnfa5Trq9wLjMpKhdLh+IFS/r5cOvdT8fYy0e3d
TNHH8LO1+/YkjNHUOtLaIih88xah284ohDPWt5N4z7JQwkb7HkIKTb/RAoGARt51
7Afx8sM+WCLMva5jTPqzXl1hQsyXzO8T4N2RuFz/pXPt8pP/OvX1khXc0I2QSYkj
lq2feRiEJnXbDa/WATNc1ohOBfBmX2YlX56UzRG9Nip+EkGT/HPBwmohIq2Ij+c4
T3AnrGAqDdW6SLhM9k1zZNli1uofW0E9cSCaGOkCgYAI29eelAwUxpbDXJB6vSyr
LBvDV+C+/3OW4AjlJ5lWSzY8oMn21Xzp03MwXOXOSFuR6vTMkJDKJ3zIDHBt9vJ2
evNmfMKzqNdsvNjORb79GAZ0paBF1XGzXvPO9JSUi6oZxNg+pW8oxIzx0xFIgtZL
PxnzFj2IbraxYwuR1CI6rQ==
-----END PRIVATE KEY-----

View File

@ -0,0 +1,24 @@
-----BEGIN CERTIFICATE-----
MIIEFDCCAvygAwIBAgIUES/M0bnsyCknA6tzY8c9dLav3BowDQYJKoZIhvcNAQEL
BQAwXjELMAkGA1UEBhMCQ04xEzARBgNVBAoTClRlbnBheS5jb20xHTAbBgNVBAsT
FFRlbnBheS5jb20gQ0EgQ2VudGVyMRswGQYDVQQDExJUZW5wYXkuY29tIFJvb3Qg
Q0EwHhcNMjMwMzAxMDExNzE2WhcNMjgwMjI4MDExNzE2WjBuMRgwFgYDVQQDDA9U
ZW5wYXkuY29tIHNpZ24xEzARBgNVBAoMClRlbnBheS5jb20xHTAbBgNVBAsMFFRl
bnBheS5jb20gQ0EgQ2VudGVyMQswCQYDVQQGDAJDTjERMA8GA1UEBwwIU2hlblpo
ZW4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvM2YfnzwBvd+B5Ro1
z+LaCVYhxia9/hQwRhi5ag2HmeZZNgMNf4+bC75Nv+t3RW0lklbM9qJSBk03eEBG
75Q33VPGy//WCJZgXlHV+rZ7ejzWtwh9Muhd60HFXhIsM3pwlfWpPo6bkJie9Na0
wMsgg/8UxsNydhEZF6HFLdqY+zqGOjRRCduIyJcFhtrkjNUMIFAOSkHBaGJjmGrm
OXigAnYAsaD1VWLOoblA0HlOs144KQ/5Shj74Ggk2pFo/YDN+i5hazHZo9hZnjmX
G5BV3KDJD0k3Gn/qymhiLlzGsYW79P+BbsmE/M6jKIP2jxcDDdpek0Z6Lk0Cz/au
02UPAgMBAAGjgbkwgbYwCQYDVR0TBAIwADALBgNVHQ8EBAMCA/gwgZsGA1UdHwSB
kzCBkDCBjaCBiqCBh4aBhGh0dHA6Ly9ldmNhLml0cnVzLmNvbS5jbi9wdWJsaWMv
aXRydXNjcmw/Q0E9MUJENDIyMEU1MERCQzA0QjA2QUQzOTc1NDk4NDZDMDFDM0U4
RUJEMiZzZz1IQUNDNDcxQjY1NDIyRTEyQjI3QTlEMzNBODdBRDFDREY1OTI2RTE0
MDM3MTANBgkqhkiG9w0BAQsFAAOCAQEAJX6C/QMYF0F3IiK9P8tW2DKN8y9vCU20
6Ws8u4bcO3AaiSmsdAJ6I1MyZkUg3dKijtnDbieY2P364IEp48TmI4k6UJwP4+/f
i0NseOm3BmAJ3mBoNmuFul+61opKpeV67AYQuhbehVA4cDPqKb/hP0tEcb6nefIO
mnys5GReLWLFV2XFR1h9QtsohPOEYlpBl6lmKHNoQZdAPtSHq2iVFLbuD7GMKoj7
Br2puJEQgxla1AsNxAYjGttEIO9I30+L5av7SKesSGXvL6G5eOvFrZl0S4EsNVz4
QeL4qmtFEKDi2eaxeyLuWe9TMI/IiI3ngekoDyTwdnUzadumbio+dg==
-----END CERTIFICATE-----

View File

@ -0,0 +1,63 @@
package weChat
import (
"context"
"errors"
"github.com/wechatpay-apiv3/wechatpay-go/core"
"github.com/wechatpay-apiv3/wechatpay-go/core/option"
"github.com/wechatpay-apiv3/wechatpay-go/services/refunddomestic"
"github.com/wechatpay-apiv3/wechatpay-go/utils"
"hospital-admin-api/config"
"path/filepath"
)
type RefundRequest struct {
TransactionId string `json:"transaction_id" comment:"微信订单号"`
OutTradeNo string `json:"out_trade_no" comment:"商户订单号"`
OutRefundNo string `json:"out_refund_no" comment:"退款订单号"`
Reason string `json:"reason" comment:"退款原因"`
PaymentAmountTotal int64 `json:"payment_amount_total" comment:"退款金额"`
NotifyUrl string `json:"notify_url" comment:"回调地址"`
}
// Refund 退款
func (r RefundRequest) Refund() (*refunddomestic.Refund, error) {
// 使用 utils 提供的函数从本地文件中加载商户私钥,商户私钥会用来生成请求的签名
certsDir := filepath.Join("extend/weChat/certs", "/1636644248/apiclient_key.pem")
mchPrivateKey, err := utils.LoadPrivateKeyWithPath(certsDir)
if err != nil {
return nil, errors.New("微信签名生成失败")
}
ctx := context.Background()
// 使用商户私钥等初始化 client并使它具有自动定时获取微信支付平台证书的能力
opts := []core.ClientOption{
option.WithWechatPayAutoAuthCipher(config.C.Wechat.MchId, config.C.Wechat.MchCertificateSerialNumber, mchPrivateKey, config.C.Wechat.V3ApiSecret),
}
client, err := core.NewClient(ctx, opts...)
if err != nil {
return nil, errors.New(err.Error())
}
refundRequest := refunddomestic.CreateRequest{
TransactionId: core.String(r.TransactionId),
OutTradeNo: core.String(r.OutTradeNo),
OutRefundNo: core.String(r.OutRefundNo),
Reason: core.String(r.Reason),
NotifyUrl: core.String(r.NotifyUrl),
Amount: &refunddomestic.AmountReq{
Currency: core.String("CNY"),
Refund: core.Int64(r.PaymentAmountTotal),
Total: core.Int64(r.PaymentAmountTotal),
},
}
svc := refunddomestic.RefundsApiService{Client: client}
resp, _, err := svc.Create(ctx, refundRequest)
if err != nil {
return nil, errors.New(err.Error())
}
return resp, nil
}

1
go.mod
View File

@ -80,6 +80,7 @@ require (
github.com/tjfoc/gmsm v1.3.2 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
github.com/wechatpay-apiv3/wechatpay-go v0.2.17 // indirect
golang.org/x/arch v0.3.0 // indirect
golang.org/x/crypto v0.9.0 // indirect
golang.org/x/image v0.7.0 // indirect

3
go.sum
View File

@ -38,6 +38,7 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/agiledragon/gomonkey v2.0.2+incompatible/go.mod h1:2NGfXu1a80LLr2cmWXGBDaHEjb1idR6+FVlX5T3D9hw=
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 h1:iC9YFYKDGEy3n/FtqJnOkZsene9olVspKmkX5A2YBEo=
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc=
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.2/go.mod h1:5JHVmnHvGzR2wNdgaW1zDLQG8kOC4Uec8ubkMogW7OQ=
@ -333,6 +334,8 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/wechatpay-apiv3/wechatpay-go v0.2.17 h1:i4YJA/6BqAbi2YfyPZBjpeEeO/+oa4UbKP4gSTRhhQg=
github.com/wechatpay-apiv3/wechatpay-go v0.2.17/go.mod h1:A254AUBVB6R+EqQFo3yTgeh7HtyqRRtN2w9hQSOrd4Q=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=