Files
shanshanzhong147 8b4e7561f4 修复: scripts 编译冲突 + order 统计 is_new 字段修正
- scripts/ 下两个独立脚本移到各自子目录,消除 package main 冲突
- model/order/model.go 统计查询 type→is_new 字段修正(7处)

Co-authored-by: multica-agent <github@multica.ai>
2026-05-26 08:06:50 -07:00

228 lines
6.0 KiB
Go

package main
import (
"encoding/json"
"flag"
"fmt"
"os"
"sort"
"strconv"
"strings"
"time"
)
type remoteOrder struct {
OrderNo string `json:"order_no"`
OutOrderNo string `json:"out_order_no"`
OrderMoney json.Number `json:"order_money"`
OrderStatus string `json:"order_status"`
PaymentTime *int64 `json:"payment_time"`
CreateTime int64 `json:"create_time"`
}
type recoveryFile struct {
Orders []recoveryOrder `json:"orders"`
}
type recoveryOrder struct {
OutOrderNo string `json:"out_order_no"`
OrderNo string `json:"order_no"`
OrderStatus string `json:"order_status"`
SubscribeId int64 `json:"subscribe_id"`
ExpireAt int64 `json:"expire_at"`
Quantity int64 `json:"quantity"`
PaidAt int64 `json:"paid_at,omitempty"`
Note string `json:"note,omitempty"`
PaymentOrderNo string `json:"payment_order_no,omitempty"`
OrderMoneyCents int64 `json:"order_money_cents,omitempty"`
}
type adminOrderResponse struct {
Code uint32 `json:"code"`
Msg string `json:"msg"`
Data struct {
Total int64 `json:"total"`
List []adminOrder `json:"list"`
} `json:"data"`
}
type adminOrder struct {
OrderNo string `json:"order_no"`
}
func main() {
var (
input string
output string
adminOrders string
subscribeID int64
onlyMissing bool
)
flag.StringVar(&input, "in", "aaa.json", "input payment platform orders JSON")
flag.StringVar(&output, "out", "internal/recovery/recovery_orders.json", "output recovery orders JSON")
flag.StringVar(&adminOrders, "admin-orders", "", "optional admin order list response JSON; when provided, matched out_order_no values are skipped")
flag.Int64Var(&subscribeID, "subscribe-id", 1, "subscribe id to use for recovered subscriptions")
flag.BoolVar(&onlyMissing, "only-missing", true, "only output payment success orders missing from admin order list when -admin-orders is set")
flag.Parse()
orders, err := readOrders(input)
must(err)
existingOrders := map[string]struct{}{}
if adminOrders != "" {
existingOrders, err = readAdminOrderSet(adminOrders)
must(err)
}
daysByAmount := map[int64]int64{
1953: 7,
4193: 30,
9093: 90,
31500: 365,
}
out := recoveryFile{Orders: make([]recoveryOrder, 0, len(orders))}
var skipped []string
var matchedExisting int
seen := make(map[string]struct{})
for _, item := range orders {
if strings.ToLower(strings.TrimSpace(item.OrderStatus)) != "success" {
continue
}
localOrderNo := strings.TrimSpace(item.OutOrderNo)
if localOrderNo == "" {
skipped = append(skipped, fmt.Sprintf("%s missing out_order_no", item.OrderNo))
continue
}
if _, ok := seen[localOrderNo]; ok {
skipped = append(skipped, fmt.Sprintf("%s duplicate out_order_no", localOrderNo))
continue
}
if _, exists := existingOrders[localOrderNo]; exists && onlyMissing {
matchedExisting++
seen[localOrderNo] = struct{}{}
continue
}
amount := decimalYuanToCents(item.OrderMoney)
days, ok := daysByAmount[amount]
if !ok {
skipped = append(skipped, fmt.Sprintf("%s unknown amount %s", localOrderNo, item.OrderMoney.String()))
continue
}
base := item.CreateTime
if item.PaymentTime != nil && *item.PaymentTime > 0 {
base = *item.PaymentTime
}
if base <= 0 {
skipped = append(skipped, fmt.Sprintf("%s missing payment/create time", localOrderNo))
continue
}
seen[localOrderNo] = struct{}{}
out.Orders = append(out.Orders, recoveryOrder{
OutOrderNo: localOrderNo,
OrderNo: strings.TrimSpace(item.OrderNo),
OrderStatus: "success",
SubscribeId: subscribeID,
ExpireAt: time.Unix(base, 0).Add(time.Duration(days) * 24 * time.Hour).UnixMilli(),
Quantity: days,
PaidAt: base,
Note: "data recovery",
PaymentOrderNo: strings.TrimSpace(item.OrderNo),
OrderMoneyCents: amount,
})
}
sort.Slice(out.Orders, func(i, j int) bool {
return out.Orders[i].OutOrderNo < out.Orders[j].OutOrderNo
})
must(writeJSON(output, out))
fmt.Printf("converted recovery orders=%d matched_existing=%d skipped=%d output=%s\n", len(out.Orders), matchedExisting, len(skipped), output)
for _, msg := range skipped {
fmt.Printf("skip: %s\n", msg)
}
}
func readAdminOrderSet(path string) (map[string]struct{}, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var resp adminOrderResponse
if err := json.Unmarshal(data, &resp); err == nil && resp.Data.List != nil {
if resp.Code != 0 && resp.Code != 200 {
return nil, fmt.Errorf("admin order response failed: code=%d msg=%s", resp.Code, resp.Msg)
}
return adminOrdersToSet(resp.Data.List), nil
}
var list []adminOrder
if err := json.Unmarshal(data, &list); err != nil {
return nil, err
}
return adminOrdersToSet(list), nil
}
func adminOrdersToSet(list []adminOrder) map[string]struct{} {
set := make(map[string]struct{}, len(list))
for _, item := range list {
orderNo := strings.TrimSpace(item.OrderNo)
if orderNo != "" {
set[orderNo] = struct{}{}
}
}
return set
}
func readOrders(path string) ([]remoteOrder, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var orders []remoteOrder
if err := json.Unmarshal(data, &orders); err != nil {
return nil, err
}
return orders, nil
}
func writeJSON(path string, data recoveryFile) error {
bytes, err := json.MarshalIndent(data, "", " ")
if err != nil {
return err
}
bytes = append(bytes, '\n')
return os.WriteFile(path, bytes, 0644)
}
func decimalYuanToCents(n json.Number) int64 {
s := strings.TrimSpace(n.String())
if s == "" {
return 0
}
parts := strings.SplitN(s, ".", 2)
yuan, _ := strconv.ParseInt(parts[0], 10, 64)
cents := yuan * 100
if len(parts) == 1 {
return cents
}
frac := parts[1]
if len(frac) > 2 {
frac = frac[:2]
}
for len(frac) < 2 {
frac += "0"
}
f, _ := strconv.ParseInt(frac, 10, 64)
if strings.HasPrefix(parts[0], "-") {
return cents - f
}
return cents + f
}
func must(err error) {
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}