package refundchannel import ( "crypto/rand" "strconv" "strings" "time" ) // 渠道退款请求号生成规则参数。 const ( // channelRefundRequestNoAlphabet 随机段字符集:大写字母与数字。 channelRefundRequestNoAlphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" // channelRefundRequestNoRandomLen 随机段长度,取渠道规则上限 18 位。 channelRefundRequestNoRandomLen = 18 // channelRefundRequestNoLength 请求号总长:前缀 4 + 日期 8 + 随机段 18。 channelRefundRequestNoLength = 30 // channelRefundRequestNoPrefixLen 前缀固定长度,不足左侧补 0,超过取前 4 位。 channelRefundRequestNoPrefixLen = 4 ) // shanghaiLocation 上海时区(东八区),用于按渠道规则生成日期段。 var shanghaiLocation = time.FixedZone("CST", 8*3600) // BuildChannelRefundRequestNo 按三渠道共性规则生成渠道退款请求号。 // // 规则与富友流水号完全一致(本包不引入渠道 SDK,因此在此独立实现同一规则): // 前缀规整为 4 位(不足左侧补 0,超过取前 4 位)+ 上海时区日期 yyyyMMdd + 18 位大写字母 // 数字随机段,总长 30。prefix 由调用方按冻结服务商类型传入:富友传机构码,其余渠道传 // 商户标识数字段。生成结果一经写入审批尝试记录即不可变,作为渠道幂等标识复用。 func BuildChannelRefundRequestNo(prefix string, now time.Time) string { var builder strings.Builder builder.Grow(channelRefundRequestNoLength) builder.WriteString(normalizeChannelRefundPrefix(prefix)) builder.WriteString(now.In(shanghaiLocation).Format("20060102")) buffer := make([]byte, channelRefundRequestNoRandomLen) if _, err := rand.Read(buffer); err != nil { // 随机源不可用时退回时间派生的同字符集随机段,保证结果仍满足格式与长度约束。 builder.WriteString(fallbackRandomSegment(now)) return builder.String() } for _, value := range buffer { builder.WriteByte(channelRefundRequestNoAlphabet[int(value)%len(channelRefundRequestNoAlphabet)]) } return builder.String() } // normalizeChannelRefundPrefix 将前缀规整为 4 位:不足左侧补 0,超过取前 4 位。 func normalizeChannelRefundPrefix(prefix string) string { normalized := strings.TrimSpace(prefix) if len(normalized) >= channelRefundRequestNoPrefixLen { return normalized[:channelRefundRequestNoPrefixLen] } return strings.Repeat("0", channelRefundRequestNoPrefixLen-len(normalized)) + normalized } // fallbackRandomSegment 生成 18 位大写字母数字随机段,仅用于随机源不可用时的兜底。 func fallbackRandomSegment(now time.Time) string { segment := strings.ToUpper(strconv.FormatInt(now.UnixNano(), 36)) segment = strings.Map(func(char rune) rune { if (char >= '0' && char <= '9') || (char >= 'A' && char <= 'Z') { return char } return 'X' }, segment) if len(segment) >= channelRefundRequestNoRandomLen { return segment[:channelRefundRequestNoRandomLen] } return segment + strings.Repeat("0", channelRefundRequestNoRandomLen-len(segment)) }