commit 3d7ca1122f002b407f0d2e7b08d6300817130434 Author: Yun Date: Sun May 3 22:40:41 2026 +0800 commit diff --git a/csvx.go b/csvx.go new file mode 100644 index 0000000..9d52838 --- /dev/null +++ b/csvx.go @@ -0,0 +1,527 @@ +package controller + +import ( + "bop_tool/app/global" + "bop_tool/app/model" + "encoding/csv" + "fmt" + "io" + "os" + "strings" + + "github.com/shopspring/decimal" +) + +type wechatBill struct{} + +func NewWechatBill() *wechatBill { + return &wechatBill{} +} + +func (b *wechatBill) Begin() { + data := readBillCsv("./resource/V2-20230817-1503284471.csv") + + // fmt.Println(data) + // _ = data + + bills, err := buildBill(data) + // fmt.Println(bills, err) + + if err != nil { + panic(err) + } + + transFee := int64(0) + refundFee := int64(0) + handFee := int64(0) + + // currency := "CNY" + + for _, bill := range bills { + + totalFee, err := decimal.NewFromString(bill.Fee) + if err != nil { + panic(err) + } + fee := totalFee.Mul(decimal.NewFromFloat(100)).IntPart() + handFee += fee + + // if bill.CashFeeType != currency { + // continue + // } + if bill.TradeState == "SUCCESS" { + // fmt.Println(bill) + // verifyOrder(bill) + + // Fee + + totalFee, err := decimal.NewFromString(bill.TotalFee) + if err != nil { + panic(err) + } + fee := totalFee.Mul(decimal.NewFromFloat(100)).IntPart() + transFee += fee + // } else if bill.TradeState == "REVOKED" { + + } else if bill.TradeState == "REFUND" || bill.TradeState == "REVOKED" { + // PROCESSING ??? + // verifyRefund(bill) + totalFee, err := decimal.NewFromString(bill.RefundFee) + if err != nil { + panic(err) + } + fee := totalFee.Mul(decimal.NewFromFloat(100)).IntPart() + refundFee += fee + } else { + panic("unknown trade state") + } + } + + fmt.Println(transFee, refundFee, transFee-refundFee, transFee-refundFee-handFee) + +} + +func verifyOrder(bill WechatBill) error { + fmt.Printf("begin %+v %+v\n", bill.OutTransactionId, bill.SubMchId) + order := model.BopOrder{} + result := global.Gorm.Where("sn = ?", bill.OutTransactionId). + Where("channel_mch_id = ?", bill.SubMchId). + First(&order) + if result.RowsAffected == 0 { + fmt.Println("order not found", bill.OutTransactionId, bill.SubMchId) + return fmt.Errorf("order not found") + } + + // 验证金额 + totalFee, err := decimal.NewFromString(bill.TotalFee) + if err != nil { + return err + } + + if order.TotalFee != totalFee.Mul(decimal.NewFromFloat(100)).IntPart() { + fmt.Println("order not equal", bill.OutTransactionId, bill.SubMchId) + return fmt.Errorf("amount not equal") + } + + if order.CashFeeType != bill.CashFeeType { + fmt.Println("cash fee type not equal", bill.OutTransactionId, bill.SubMchId) + return fmt.Errorf("cash fee type not equal") + } + + if order.TradeState != "SUCCESS" && order.TradeState != "REFUND" { + fmt.Println("trade state not equal", bill.OutTransactionId, bill.SubMchId) + return fmt.Errorf("trade state not equal") + } + + return nil +} + +func verifyRefund(bill WechatBill) {} + +// type Order struct { +// TransTime int64 `json:"trans_time"` // Transaction time +// Appid string `json:"appid"` // Official account ID(appid) +// MchId string `json:"mch_id"` // Vendor ID(mch_id) +// SubMchId string `json:"sub_mch_id"` // Sub vendor ID(sub_mch_id) +// DeviceInfo string `json:"device_info"` // Device ID(Device_info) +// TransactionId string `json:"transaction_id"` // Wechat order number(transaction_id) +// OutTransactionId string `json:"out_transaction_id"` // Vendor order number(out_transaction_id) +// Openid string `json:"openid"` // User tag(openid) +// TradeType string `json:"trade_type"` // Transaction type(trade_type) +// TradeState string `json:"trade_state"` // Transaction status(trade_state) +// BankType string `json:"bank_type"` // Payment bank(bank_type) +// FeeType string `json:"fee_type"` // Currency type(fee_type) +// TotalFee int64 `json:"total_fee"` // Total amount(total_fee) +// CouponAmount int64 `json:"coupon_amount"` // Coupon amount +// //RefundId string `json:"refund_id"` // Wechat refund number(refund_id) +// //OutRefundNo string `json:"out_refund_no"` // Vendor refund number(out_refund_no) +// //RefundFee string `json:"refund_fee"` // Refund amount(refund_fee) +// //CouponRefundAmount string `json:"coupon_refund_amount"` // Coupon refund amount +// //RefundType string `json:"refund_type"` // Refund type +// //RefundStatus string `json:"refund_status"` // Refund status(refund_status) +// ProductName string `json:"product_name"` // Product name +// Attach string `json:"attach"` // Vendor's data package(attach) +// Fee string `json:"fee"` // Fee +// Rate string `json:"rate"` // Rate +// CashFeeType string `json:"cash_fee_type"` // Payment Currency type(Cash_fee_type) +// CashFee int64 `json:"cash_fee"` // Cash payment amount(Cash_fee) +// SettlementCurrencyType string `json:"settlement_currency_type"` // Settlement currency type +// SettlementCurrencyAmount int64 `json:"settlement_currency_amount"` // Settlement currency amount +// ExchangeRate string `json:"exchange_rate"` // Exchange rate +// //RefundExchangeRate string `json:"refund_exchange_rate"` // Refund exchange rate +// //PayerRefundAmount string `json:"payer_refund_amount"` // Payer's Refund amount +// //PayerRefundCurrencyType string `json:"payer_refund_currency_type"` // Payer's Refund currency type +// //RefundCurrencyType string `json:"refund_currency_type"` // Refund currency type +// //RefundSettlementCurrencyType string `json:"refund_settlement_currency_type"` // Refund settlement currency type +// //RefundSettlementAmount string `json:"refund_settlement_amount"` // Refund settlement amount +// } + +// func buildOrder(data []WechatBill) ([]Order, error) { +// resp := []Order{} +// for _,b := range data { +// o := &Order{} +// t,err:=time.ParseInLocation("2006-01-02 15:04:05", b.TransTime, time.Local) +// if err != nil { +// return nil, err +// } +// o.TransTime = t.Unix() +// o.Appid = b.Appid +// o.MchId = b.MchId +// o.SubMchId = b.SubMchId +// o.DeviceInfo = b.DeviceInfo +// o.TransactionId = b.TransactionId +// o.OutTransactionId = b.OutTransactionId +// o.Openid = b.Openid +// o.TradeType = b.TradeType +// o.TradeState = b.TradeState + +// } +// return resp, nil +// } + +func readBillCsv(path string) []map[string]string { + file, err := os.Open(path) + if err != nil { + panic(err) + } + defer file.Close() + + reader := csv.NewReader(file) + + header := []string{} + data := []map[string]string{} + + for { + record, err := reader.Read() + if err == io.EOF { + break + } + if err != nil { + panic(err) + } + // fmt.Println(record) + + if len(header) == 0 { + header = record + for k, v := range header { + // 去掉BOM头 + v = strings.Trim(v, "\xEF\xBB\xBF") + header[k] = v + } + + continue + } + + m := map[string]string{} + for i, v := range header { + m[v] = strings.Trim(record[i], "`") + } + data = append(data, m) + } + + return data +} + +// func preNUm(data byte) int { +// str := fmt.Sprintf("%b", data) +// var i int = 0 +// for i < len(str) { +// if str[i] != '1' { +// break +// } +// i++ +// } +// return i +// } +// func isUtf8(data []byte) bool { +// for i := 0; i < len(data); { +// if data[i]&0x80 == 0x00 { +// // 0XXX_XXXX +// i++ +// continue +// } else if num := preNUm(data[i]); num > 2 { +// // 110X_XXXX 10XX_XXXX +// // 1110_XXXX 10XX_XXXX 10XX_XXXX +// // 1111_0XXX 10XX_XXXX 10XX_XXXX 10XX_XXXX +// // 1111_10XX 10XX_XXXX 10XX_XXXX 10XX_XXXX 10XX_XXXX +// // 1111_110X 10XX_XXXX 10XX_XXXX 10XX_XXXX 10XX_XXXX 10XX_XXXX +// // preNUm() 返回首个字节的8个bits中首个0bit前面1bit的个数,该数量也是该字符所使用的字节数 +// i++ +// for j := 0; j < num-1; j++ { +// //判断后面的 num - 1 个字节是不是都是10开头 +// if data[i]&0xc0 != 0x80 { +// return false +// } +// i++ +// } +// } else { +// //其他情况说明不是utf-8 +// return false +// } +// } +// return true +// } + +// 订单 +type WechatBill struct { + TransTime string `json:"trans_time"` // Transaction time + Appid string `json:"appid"` // Official account ID(appid) + MchId string `json:"mch_id"` // Vendor ID(mch_id) + SubMchId string `json:"sub_mch_id"` // Sub vendor ID(sub_mch_id) + DeviceInfo string `json:"device_info"` // Device ID(Device_info) + TransactionId string `json:"transaction_id"` // Wechat order number(transaction_id) + OutTransactionId string `json:"out_transaction_id"` // Vendor order number(out_transaction_id) + Openid string `json:"openid"` // User tag(openid) + TradeType string `json:"trade_type"` // Transaction type(trade_type) + TradeState string `json:"trade_state"` // Transaction status(trade_state) + BankType string `json:"bank_type"` // Payment bank(bank_type) + FeeType string `json:"fee_type"` // Currency type(fee_type) + TotalFee string `json:"total_fee"` // Total amount(total_fee) + CouponAmount string `json:"coupon_amount"` // Coupon amount + RefundId string `json:"refund_id"` // Wechat refund number(refund_id) + OutRefundNo string `json:"out_refund_no"` // Vendor refund number(out_refund_no) + RefundFee string `json:"refund_fee"` // Refund amount(refund_fee) + CouponRefundAmount string `json:"coupon_refund_amount"` // Coupon refund amount + RefundType string `json:"refund_type"` // Refund type + RefundStatus string `json:"refund_status"` // Refund status(refund_status) + ProductName string `json:"product_name"` // Product name + Attach string `json:"attach"` // Vendor's data package(attach) + Fee string `json:"fee"` // Fee + Rate string `json:"rate"` // Rate + CashFeeType string `json:"cash_fee_type"` // Payment Currency type(Cash_fee_type) + CashFee string `json:"cash_fee"` // Cash payment amount(Cash_fee) + SettlementCurrencyType string `json:"settlement_currency_type"` // Settlement currency type + SettlementCurrencyAmount string `json:"settlement_currency_amount"` // Settlement currency amount + ExchangeRate string `json:"exchange_rate"` // Exchange rate + RefundExchangeRate string `json:"refund_exchange_rate"` // Refund exchange rate + PayerRefundAmount string `json:"payer_refund_amount"` // Payer's Refund amount + PayerRefundCurrencyType string `json:"payer_refund_currency_type"` // Payer's Refund currency type + RefundCurrencyType string `json:"refund_currency_type"` // Refund currency type + RefundSettlementCurrencyType string `json:"refund_settlement_currency_type"` // Refund settlement currency type + RefundSettlementAmount string `json:"refund_settlement_amount"` // Refund settlement amount +} + +func buildBill(data []map[string]string) ([]WechatBill, error) { + resps := []WechatBill{} + + for _, val := range data { + val := val + r := WechatBill{} + + s, ok := val["Transaction time"] + if !ok { + return nil, fmt.Errorf("Transaction time not found") + } + r.TransTime = s + + s, ok = val["Official account ID(appid)"] + if !ok { + return nil, fmt.Errorf("Official account ID(appid) not found") + } + r.Appid = s + + s, ok = val["Vendor ID(mch_id)"] + if !ok { + return nil, fmt.Errorf("Vendor ID(mch_id) not found") + } + r.MchId = s + + s, ok = val["Sub vendor ID(sub_mch_id)"] + if !ok { + return nil, fmt.Errorf("Sub vendor ID(sub_mch_id) not found") + } + r.SubMchId = s + + s, ok = val["Device ID(Device_info)"] + if !ok { + return nil, fmt.Errorf("Device ID(Device_info) not found") + } + r.DeviceInfo = s + + s, ok = val["Wechat order number(transaction_id)"] + if !ok { + return nil, fmt.Errorf("Wechat order number(transaction_id) not found") + } + r.TransactionId = s + + s, ok = val["Vendor order number(out_transaction_id)"] + if !ok { + return nil, fmt.Errorf("Vendor order number(out_transaction_id) not found") + } + + r.OutTransactionId = s + + s, ok = val["User tag(openid)"] + if !ok { + return nil, fmt.Errorf("User tag(openid) not found") + } + r.Openid = s + + s, ok = val["Transaction type(trade_type)"] + if !ok { + return nil, fmt.Errorf("Transaction type(trade_type) not found") + } + r.TradeType = s + + s, ok = val["Transaction status(trade_state)"] + if !ok { + return nil, fmt.Errorf("Transaction status(trade_state) not found") + } + r.TradeState = s + + s, ok = val["Payment bank(bank_type)"] + if !ok { + return nil, fmt.Errorf("Payment bank(bank_type) not found") + } + r.BankType = s + + s, ok = val["Currency type(fee_type)"] + if !ok { + return nil, fmt.Errorf("Currency type(fee_type) not found") + } + r.FeeType = s + + s, ok = val["Total amount(total_fee)"] + if !ok { + return nil, fmt.Errorf("Total amount(total_fee) not found") + } + r.TotalFee = s + + s, ok = val["Coupon amount"] + if !ok { + return nil, fmt.Errorf("Coupon amount not found") + } + r.CouponAmount = s + + s, ok = val["Wechat refund number(refund_id)"] + if !ok { + return nil, fmt.Errorf("Wechat refund number(refund_id) not found") + } + r.RefundId = s + + s, ok = val["Vendor refund number(out_refund_no)"] + if !ok { + return nil, fmt.Errorf("Vendor refund number(out_refund_no) not found") + } + r.OutRefundNo = s + + s, ok = val["Refund amount(refund_fee)"] + if !ok { + return nil, fmt.Errorf("Refund amount(refund_fee) not found") + } + r.RefundFee = s + + s, ok = val["Coupon refund amount"] + if !ok { + return nil, fmt.Errorf("Coupon refund amount not found") + } + r.CouponRefundAmount = s + + s, ok = val["Refund type"] + if !ok { + return nil, fmt.Errorf("Refund type not found") + } + r.RefundType = s + + s, ok = val["Refund status(refund_status)"] + if !ok { + return nil, fmt.Errorf("Refund status(refund_status) not found") + } + r.RefundStatus = s + + s, ok = val["Product name"] + if !ok { + return nil, fmt.Errorf("Product name not found") + } + r.ProductName = s + + s, ok = val["Vendor's data package(attach)"] + if !ok { + return nil, fmt.Errorf("Vendor's data package(attach) not found") + } + r.Attach = s + + s, ok = val["Fee"] + if !ok { + return nil, fmt.Errorf("Fee not found") + } + r.Fee = s + + s, ok = val["Rate"] + if !ok { + return nil, fmt.Errorf("Rate not found") + } + r.Rate = s + + s, ok = val["Payment Currency type(Cash_fee_type)"] + if !ok { + return nil, fmt.Errorf("Payment Currency type(Cash_fee_type) not found") + } + r.CashFeeType = s + + s, ok = val["Cash payment amount(Cash_fee)"] + if !ok { + return nil, fmt.Errorf("Cash payment amount(Cash_fee) not found") + } + r.CashFee = s + + s, ok = val["Settlement currency type"] + if !ok { + return nil, fmt.Errorf("Settlement currency type not found") + } + r.SettlementCurrencyType = s + + s, ok = val["Settlement currency amount"] + if !ok { + return nil, fmt.Errorf("Settlement currency amount not found") + } + r.SettlementCurrencyAmount = s + + s, ok = val["Exchange rate"] + if !ok { + return nil, fmt.Errorf("Exchange rate not found") + } + r.ExchangeRate = s + + s, ok = val["Refund exchange rate"] + if !ok { + return nil, fmt.Errorf("Refund exchange rate not found") + } + r.RefundExchangeRate = s + + s, ok = val["Payer's Refund amount"] + if !ok { + return nil, fmt.Errorf("Payer's Refund amount not found") + } + r.PayerRefundAmount = s + + s, ok = val["Payer's Refund currency type"] + if !ok { + return nil, fmt.Errorf("Payer's Refund currency type not found") + } + r.PayerRefundCurrencyType = s + + s, ok = val["Refund currency type"] + if !ok { + return nil, fmt.Errorf("Refund currency type not found") + } + r.RefundCurrencyType = s + + s, ok = val["Refund settlement currency type"] + if !ok { + return nil, fmt.Errorf("Refund settlement currency type not found") + } + r.RefundSettlementCurrencyType = s + + s, ok = val["Refund settlement amount"] + if !ok { + return nil, fmt.Errorf("Refund settlement amount not found") + } + r.RefundSettlementAmount = s + + resps = append(resps, r) + + } + + return resps, nil +}