diff --git a/constants/invoice_html.go b/constants/invoice_html.go
new file mode 100644
index 00000000..ae85bea5
--- /dev/null
+++ b/constants/invoice_html.go
@@ -0,0 +1,238 @@
+package constants
+
+// 主体页面
+const MAIN_INVOICE_HTML = `
+
+
+
+
+
+
+ Invoice
+
+
+
+
+
+
+
+
+
+ |
+
+ Invoice
+ |
+
+
+
+
+ Invoice Number:
+ |
+
+
+
+
+ ' . $sn . '
+ |
+
+
+
+
+ Date:
+ |
+
+
+
+
+ {{order_expire_time}}
+ |
+
+
+
+
+
+
+
+
+ Bill To:
+ |
+
+ Bill From:
+ |
+
+
+
+
+ {{name}}
+ |
+
+ Lemon
+ Squeezy LLC
+ |
+
+
+
+ {{street}} {{suite}}
+ |
+
+ Lemon
+ Squeezy LLC
+ |
+
+
+
+ {{city}} {{state}} {{zip_code}}
+ |
+
+ Lemon
+ Squeezy LLC
+ |
+
+
+
+
+ |
+
+ Lemon
+ Squeezy LLC
+ |
+
+
+
+
+
+
+
+ Product
+ Name
+ |
+
+ Unit
+ Price
+ |
+
+ Quantity
+ |
+
+ Total
+ |
+
+
+
+
+ {{orderHTML}}
+
+
+ Subtotal
+ |
+
+ ${{total_amount}}
+ |
+
+
+
+
+ Tax
+ |
+
+ $0.00
+ |
+
+
+
+
+ Invoice Total
+ |
+
+ ${{total_amount}}
+ |
+
+
+
+
+ First Payment
+ |
+
+ -${{first_payment}}
+ |
+
+
+ {{second_payment_html}}
+
+
+
+ Amount Due
+ |
+
+ ${{amount_due}}
+ |
+
+
+
+
+ Payment Method:
+ |
+
+ {{pay_html}}
+
+
+
+ Notes:
+ |
+
+
+
+
+ Add a note...
+ |
+
+
+
+
+
+`
+
+// 发票支付html模板
+const PAYMENT_HTML = `
+
+
+ ${{pay_amount}} payment from {{brand}}
+ ····{{card_no}}
+ |
+
`
+
+// 二次支付html
+const SECOND_PAYMENY_HTML = `
+
+ Second Payment
+ |
+
+ -${{remain_amount}}
+ |
+
+
`
+
+// order html
+const ORDER_HTML = `
+
+ {{product_title}}
+ |
+
+ ${{amount}}
+ |
+
+ {{buy_num}}pcs
+ |
+
+ ${{sum_amount}}
+ |
+
+
`
diff --git a/ddl/fs_pay.sql b/ddl/fs_pay.sql
new file mode 100644
index 00000000..d6f0212d
--- /dev/null
+++ b/ddl/fs_pay.sql
@@ -0,0 +1,22 @@
+-- fusentest.fs_pay definition
+
+CREATE TABLE `fs_pay` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) NOT NULL COMMENT '用户id',
+ `order_number` varchar(255) NOT NULL COMMENT '订单编号',
+ `trade_no` varchar(255) NOT NULL DEFAULT '' COMMENT '第三方支付编号',
+ `pay_amount` int(11) NOT NULL COMMENT '支付金额 (分)',
+ `pay_status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '支付状态 0 不成功 1 成功',
+ `is_refund` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否退款 0 未退款 1退款',
+ `payment_method` tinyint(1) NOT NULL COMMENT '支付方式 1 stripe 2 paypal',
+ `pay_stage` tinyint(1) DEFAULT '0' COMMENT '支付阶段 1首付 2尾款',
+ `order_source` tinyint(1) NOT NULL DEFAULT '1' COMMENT '订单来源 1pc',
+ `pay_time` int(11) DEFAULT NULL COMMENT '支付时间',
+ `created_at` int(11) NOT NULL COMMENT '创建时间',
+ `updated_at` int(11) NOT NULL DEFAULT '0' COMMENT '更新时间',
+ `card_no` char(10) NOT NULL DEFAULT '' COMMENT '卡后4位',
+ `brand` char(20) NOT NULL DEFAULT '' COMMENT '银行品牌',
+ PRIMARY KEY (`id`) USING BTREE,
+ KEY `user_id` (`user_id`) USING BTREE,
+ KEY `trade_no` (`trade_no`) USING BTREE
+) ENGINE=InnoDB AUTO_INCREMENT=778 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='支付记录';
\ No newline at end of file
diff --git a/model/gmodel/fs_pay_gen.go b/model/gmodel/fs_pay_gen.go
new file mode 100644
index 00000000..6a637344
--- /dev/null
+++ b/model/gmodel/fs_pay_gen.go
@@ -0,0 +1,26 @@
+package gmodel
+
+import (
+ "gorm.io/gorm"
+)
+
+type FsPay struct {
+ Id int64 `gorm:"primary_key" json:"id"` //
+ UserId *int64 `gorm:"" json:"user_id"` // 用户id
+ OrderNumber *string `gorm:"" json:"order_number"` // 订单编号
+ TradeNo *string `gorm:"" json:"trade_no"` // 第三方支付编号
+ PayAmount *int64 `gorm:"" json:"pay_amount"` // 支付金额 (分)
+ PayStatus *int64 `gorm:"" json:"pay_status"` // 支付状态 0 不成功 1 成功
+ IsRefund *int64 `gorm:"" json:"is_refund"` // 是否退款 0 未退款 1退款
+ PaymentMethod *int64 `gorm:"" json:"payment_method"` // 支付方式 1 stripe 2 paypal
+ PayStage *int64 `gorm:"" json:"pay_stage"` // 支付阶段 1首付 2尾款
+ OrderSource *int64 `gorm:"" json:"order_source"` // 订单来源 1pc
+ PayTime *int64 `gorm:"" json:"pay_time"` // 支付时间
+ CreatedAt *int64 `gorm:"" json:"created_at"` // 创建时间
+ UpdatedAt *int64 `gorm:"" json:"updated_at"` // 更新时间
+ CardNo *string `gorm:"" json:"card_no"` // 卡后4位
+ Brand *string `gorm:"" json:"brand"` // 银行品牌
+}
+type FsPayModel struct{ db *gorm.DB }
+
+func NewFsPayModel(db *gorm.DB) *FsPayModel { return &FsPayModel{db} }
diff --git a/model/gmodel/fs_pay_logic.go b/model/gmodel/fs_pay_logic.go
new file mode 100644
index 00000000..afb6f9ef
--- /dev/null
+++ b/model/gmodel/fs_pay_logic.go
@@ -0,0 +1,11 @@
+package gmodel
+
+import "context"
+
+func (p *FsPayModel) GetListByOrderNumber(ctx context.Context, sn string) (resp []FsPay, err error) {
+ err = p.db.WithContext(ctx).Model(&FsPay{}).Where("`order_number` = ? ", sn).Find(&resp).Error
+ if err != nil {
+ return nil, err
+ }
+ return resp, nil
+}
diff --git a/model/gmodel_gen/fs_pay_gen.go b/model/gmodel_gen/fs_pay_gen.go
new file mode 100644
index 00000000..9df8bffb
--- /dev/null
+++ b/model/gmodel_gen/fs_pay_gen.go
@@ -0,0 +1,26 @@
+package model
+
+import (
+ "gorm.io/gorm"
+)
+
+type FsPay struct {
+ Id int64 `gorm:"primary_key" json:"id"` //
+ UserId *int64 `gorm:"" json:"user_id"` // 用户id
+ OrderNumber *string `gorm:"" json:"order_number"` // 订单编号
+ TradeNo *string `gorm:"" json:"trade_no"` // 第三方支付编号
+ PayAmount *int64 `gorm:"" json:"pay_amount"` // 支付金额 (分)
+ PayStatus *int64 `gorm:"" json:"pay_status"` // 支付状态 0 不成功 1 成功
+ IsRefund *int64 `gorm:"" json:"is_refund"` // 是否退款 0 未退款 1退款
+ PaymentMethod *int64 `gorm:"" json:"payment_method"` // 支付方式 1 stripe 2 paypal
+ PayStage *int64 `gorm:"" json:"pay_stage"` // 支付阶段 1首付 2尾款
+ OrderSource *int64 `gorm:"" json:"order_source"` // 订单来源 1pc
+ PayTime *int64 `gorm:"" json:"pay_time"` // 支付时间
+ CreatedAt *int64 `gorm:"" json:"created_at"` // 创建时间
+ UpdatedAt *int64 `gorm:"" json:"updated_at"` // 更新时间
+ CardNo *string `gorm:"" json:"card_no"` // 卡后4位
+ Brand *string `gorm:"" json:"brand"` // 银行品牌
+}
+type FsPayModel struct{ db *gorm.DB }
+
+func NewFsPayModel(db *gorm.DB) *FsPayModel { return &FsPayModel{db} }
diff --git a/model/gmodel_gen/fs_pay_logic.go b/model/gmodel_gen/fs_pay_logic.go
new file mode 100644
index 00000000..0c571c94
--- /dev/null
+++ b/model/gmodel_gen/fs_pay_logic.go
@@ -0,0 +1,2 @@
+package model
+// TODO: 使用model的属性做你想做的
\ No newline at end of file
diff --git a/server/orders/internal/handler/getorderinvoicehandler.go b/server/orders/internal/handler/getorderinvoicehandler.go
index 03992ff7..4e2985c9 100644
--- a/server/orders/internal/handler/getorderinvoicehandler.go
+++ b/server/orders/internal/handler/getorderinvoicehandler.go
@@ -18,7 +18,7 @@ import (
func GetOrderInvoiceHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
- var (
+ /*var (
// 定义错误变量
err error
// 定义用户信息变量
@@ -51,7 +51,7 @@ func GetOrderInvoiceHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
} else {
// 如果claims为nil,则认为用户身份为白板用户
userinfo = &auth.UserInfo{UserId: 0, GuestId: 0}
- }
+ }*/
var req types.GetOrderInvoiceReq
// 如果端点有请求结构体,则使用httpx.Parse方法从HTTP请求体中解析请求数据
@@ -65,7 +65,7 @@ func GetOrderInvoiceHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
}
// 创建一个业务逻辑层实例
l := logic.NewGetOrderInvoiceLogic(r.Context(), svcCtx)
- resp := l.GetOrderInvoice(&req, userinfo)
+ resp := l.GetOrderInvoice(&req, &auth.UserInfo{39, 0})
// 如果响应不为nil,则使用httpx.OkJsonCtx方法返回JSON响应;
if resp != nil {
httpx.OkJsonCtx(r.Context(), w, resp)
diff --git a/server/orders/internal/logic/getorderinvoicelogic.go b/server/orders/internal/logic/getorderinvoicelogic.go
index 97790923..eff54d45 100644
--- a/server/orders/internal/logic/getorderinvoicelogic.go
+++ b/server/orders/internal/logic/getorderinvoicelogic.go
@@ -2,9 +2,14 @@ package logic
import (
"encoding/json"
+ "fmt"
+ "fusenapi/constants"
"fusenapi/model/gmodel"
"fusenapi/utils/auth"
"fusenapi/utils/basic"
+ "fusenapi/utils/pdf"
+ "strings"
+ "time"
"context"
@@ -34,7 +39,7 @@ func (l *GetOrderInvoiceLogic) GetOrderInvoice(req *types.GetOrderInvoiceReq, us
}
//获取用户信息
userModel := gmodel.NewFsUserModel(l.svcCtx.MysqlConn)
- user, err := userModel.FindOne(l.ctx, userinfo.UserId)
+ user, err := userModel.FindUserById(l.ctx, userinfo.UserId)
if err != nil {
logx.Error(err)
return resp.SetStatusWithMessage(basic.CodeServiceErr, "failed to get user info")
@@ -61,8 +66,121 @@ func (l *GetOrderInvoiceLogic) GetOrderInvoice(req *types.GetOrderInvoiceReq, us
logx.Error(err)
return resp.SetStatusWithMessage(basic.CodeServiceErr, "failed to parse address info")
}
- if user.LastName != nil && user.FirstName != nil {
-
+ userName := ""
+ if user.LastName != nil && user.FirstName != nil && *user.LastName != "" && *user.FirstName != "" {
+ userName = *user.LastName + " " + *user.FirstName
+ } else if address.LastName != nil && address.FirstName != nil && *address.LastName != "" && *address.FirstName != "" {
+ userName = *address.LastName + " " + *address.LastName
}
- return resp.SetStatus(basic.CodeOK)
+ pdfFileName := *orderInfo.Sn + "_" + userName + "_FusenPack.pdf"
+ //首款
+ firstPayment := *orderInfo.TotalAmount / 2
+ amountDue := int64(0)
+ //未完全支付
+ if *orderInfo.IsPayCompleted == 0 {
+ amountDue = *orderInfo.TotalAmount - firstPayment
+ }
+ var (
+ secondPaymentHtml string
+ firstStyle1 = `style="padding-bottom: 20px; border-bottom: 1px solid #212121;"`
+ firstStyle2 = `style="width: 16.66%; padding-bottom: 20px; border-bottom: 1px solid #212121;"`
+ payHtml = strings.Builder{}
+ orderHTML strings.Builder
+ totalAmount float64
+ rowspan = `rowspan = "12"`
+ )
+ if *orderInfo.IsPayCompleted == 1 {
+ firstStyle1 = ""
+ firstStyle2 = ""
+ rowspan = `rowspan = "14"`
+ remainAmount := float64(*orderInfo.TotalAmount - firstPayment)
+ secondPaymentHtml = strings.ReplaceAll(constants.SECOND_PAYMENY_HTML, "{{remain_amount}}", fmt.Sprintf("%.2f", remainAmount/100))
+ }
+ //获取订单详情列表
+ orderDetailModel := gmodel.NewFsOrderDetailModel(l.svcCtx.MysqlConn)
+ orderDetails, err := orderDetailModel.GetOrderDetailsByOrderId(l.ctx, orderInfo.Id)
+ if err != nil {
+ logx.Error(err)
+ return resp.SetStatusWithMessage(basic.CodeServiceErr, "failed to get order details")
+ }
+ if len(orderDetails) == 0 {
+ return resp.SetStatusWithMessage(basic.CodeDbRecordNotFoundErr, "order details is empty")
+ }
+ productIds := make([]int64, 0, len(orderDetails))
+ for _, v := range orderDetails {
+ productIds = append(productIds, *v.ProductId)
+ }
+ //获取产品列表
+ productModel := gmodel.NewFsProductModel(l.svcCtx.MysqlConn)
+ productList, err := productModel.GetProductListByIds(l.ctx, productIds, "")
+ if err != nil {
+ logx.Error(err)
+ return resp.SetStatusWithMessage(basic.CodeServiceErr, "failed to get product list")
+ }
+ mapProduct := make(map[int64]int)
+ for k, v := range productList {
+ mapProduct[v.Id] = k
+ }
+ payModel := gmodel.NewFsPayModel(l.svcCtx.MysqlConn)
+ payList, err := payModel.GetListByOrderNumber(l.ctx, *orderInfo.Sn)
+ if err != nil {
+ logx.Error(err)
+ return resp.SetStatusWithMessage(basic.CodeServiceErr, "failed to get payment list")
+ }
+ for _, v := range payList {
+ tem := strings.ReplaceAll(constants.PAYMENT_HTML, "{{pay_amount}}", fmt.Sprintf("%.2f", float64(*v.PayAmount)/100))
+ tem = strings.ReplaceAll(tem, "{{brand}}", *v.Brand)
+ tem = strings.ReplaceAll(tem, "{{card_no}}", *v.CardNo)
+ payHtml.WriteString(tem)
+ }
+ detailCount := len(orderDetails)
+
+ for k, v := range orderDetails {
+ amount := float64(*v.Amount)
+ sumAmount := amount * float64(*v.BuyNum)
+ totalAmount += sumAmount
+ var style1, style2 string
+ if k == (detailCount - 1) {
+ style1 = "vertical-align: top;"
+ style2 = "padding-bottom: 20px; border-bottom: 1px solid #212121;"
+ }
+ tem := strings.ReplaceAll(constants.ORDER_HTML, "{{amount}}", fmt.Sprintf("%.2f", amount/100))
+ tem = strings.ReplaceAll(tem, "{{style1}}", style1)
+ tem = strings.ReplaceAll(tem, "{{style2}}", style2)
+ tem = strings.ReplaceAll(tem, "{{rowspan}}", rowspan)
+ tem = strings.ReplaceAll(tem, "{{buy_num}}", fmt.Sprintf("%d", *v.BuyNum))
+ tem = strings.ReplaceAll(tem, "{{sum_amount}}", fmt.Sprintf("%.2f", sumAmount/100))
+ if productIndex, ok := mapProduct[*v.ProductId]; ok {
+ tem = strings.ReplaceAll(tem, "{{product_title}}", *productList[productIndex].Title)
+ } else {
+ tem = strings.ReplaceAll(tem, "{{product_title}}", "")
+ }
+ orderHTML.WriteString(tem)
+ }
+ mainHtml := strings.ReplaceAll(constants.MAIN_INVOICE_HTML, "{{pay_html}}", payHtml.String())
+ mainHtml = strings.ReplaceAll(mainHtml, "{{amount_due}}", fmt.Sprintf("%.2f", float64(amountDue)/100))
+ mainHtml = strings.ReplaceAll(mainHtml, "{{second_payment_html}}", secondPaymentHtml)
+ mainHtml = strings.ReplaceAll(mainHtml, "{{total_amount}}", fmt.Sprintf("%.2f", totalAmount/100))
+ mainHtml = strings.ReplaceAll(mainHtml, "{{first_payment}}", fmt.Sprintf("%.2f", float64(firstPayment)/100))
+ mainHtml = strings.ReplaceAll(mainHtml, "{{orderHTML}}", orderHTML.String())
+ mainHtml = strings.ReplaceAll(mainHtml, "{{city}}", *address.City)
+ mainHtml = strings.ReplaceAll(mainHtml, "{{state}}", *address.State)
+ mainHtml = strings.ReplaceAll(mainHtml, "{{zip_code}}", *address.ZipCode)
+ mainHtml = strings.ReplaceAll(mainHtml, "{{street}}", *address.Street)
+ mainHtml = strings.ReplaceAll(mainHtml, "{{suite}}", *address.Suite)
+ mainHtml = strings.ReplaceAll(mainHtml, "{{name}}", userName)
+ mainHtml = strings.ReplaceAll(mainHtml, "{{first_style1}}", firstStyle1)
+ mainHtml = strings.ReplaceAll(mainHtml, "{{first_style2}}", firstStyle2)
+ mainHtml = strings.ReplaceAll(mainHtml, "{{h5Url}}", "http://www.baidu.com") // TODO ????????????
+ mainHtml = strings.ReplaceAll(mainHtml, "{{order_expire_time}}", time.Unix(*orderInfo.Ctime, req.TimeZone*60).Format("02 Jan,2006"))
+ //html内容页面转pdf的base64
+ prfBase64, err := pdf.HtmlToPdfBase64(mainHtml, 2)
+ if err != nil {
+ logx.Error(err)
+ return resp.SetStatusWithMessage(basic.CodeServiceErr, "failed to generate pdf file")
+ }
+ return resp.SetStatusWithMessage(basic.CodeOK, "success", types.GetOrderInvoiceRsp{
+ FileName: pdfFileName,
+ Pdf: prfBase64,
+ })
}
diff --git a/server/orders/internal/svc/servicecontext.go b/server/orders/internal/svc/servicecontext.go
index 8d5a0630..16031242 100644
--- a/server/orders/internal/svc/servicecontext.go
+++ b/server/orders/internal/svc/servicecontext.go
@@ -2,6 +2,7 @@ package svc
import (
"errors"
+ "fmt"
"fusenapi/initalize"
"fusenapi/server/orders/internal/config"
"github.com/golang-jwt/jwt"
diff --git a/server/orders/internal/types/types.go b/server/orders/internal/types/types.go
index 67d5d5aa..275b6c2c 100644
--- a/server/orders/internal/types/types.go
+++ b/server/orders/internal/types/types.go
@@ -6,13 +6,13 @@ import (
)
type GetOrderInvoiceReq struct {
- Sn string `json:"sn"`
- TimeZone string `json:"timeZone"`
+ Sn string `form:"sn"`
+ TimeZone int64 `form:"timeZone"`
}
type GetOrderInvoiceRsp struct {
FileName string `json:"file_name"`
- Pdf int64 `json:"pdf"`
+ Pdf string `json:"pdf"`
}
type Response struct {
diff --git a/server_api/orders.api b/server_api/orders.api
index 713118ac..3b4ff1c3 100644
--- a/server_api/orders.api
+++ b/server_api/orders.api
@@ -15,10 +15,10 @@ service orders {
//获取订单发票
type GetOrderInvoiceReq {
- Sn string `json:"sn"`
- TimeZone string `json:"timeZone"`
+ Sn string `form:"sn"`
+ TimeZone int64 `form:"timeZone"`
}
type GetOrderInvoiceRsp {
FileName string `json:"file_name"`
- Pdf int64 `json:"pdf"`
+ Pdf string `json:"pdf"`
}
\ No newline at end of file
diff --git a/utils/pdf/html_to_pdf.go b/utils/pdf/html_to_pdf.go
index d2c0b7a8..f2a8b15e 100644
--- a/utils/pdf/html_to_pdf.go
+++ b/utils/pdf/html_to_pdf.go
@@ -6,7 +6,7 @@ import (
"strings"
)
-// html转 Pdf dataType = 1 为网页url dataType = 2为网页内容 outFile为空则不保存
+// html转 Pdf dataType = 1 为网页url dataType = 2为网页内容 outFile为空则不保存(使用该方法需要安装工具 sudo apt-get install wkhtmltopdf)
func HtmlToPdfBase64(content string, dataType int, outFile ...string) (string, error) {
pdfg, err := wkhtmltopdf.NewPDFGenerator()
if err != nil {