e128c96d22
Store 挂库 store(NewStore 内部构造 libcodes.Store,签名不变); generator.go 三函数委托 libcodes,ErrDuplicate 别名同一哨兵; Service.CreateBatch 走 libcodes.Mint(签名不变)。openMigratedSQLite 挪到共享 sqlite_helper_test.go,backfill_test.go/service_sqlite_test.go 共用。Store.CreateBatch/CreateCode/CodeExistsByHash 原样保留供 webhook.go(Task 6 改用新原语,Task 7 删除)。
42 lines
1.7 KiB
Go
42 lines
1.7 KiB
Go
// Package codes implements the activation-code lifecycle:
|
|
// batch generation, card-store webhook ingestion, idempotent redemption,
|
|
// subscription extension, and CSV export.
|
|
//
|
|
// Security invariant: plaintext codes are NEVER written to the database or
|
|
// to any log. The database stores only SHA-256(canonical_plaintext).
|
|
// Plaintext appears exactly once: in the batch-generation response / CSV export.
|
|
package codes
|
|
|
|
import (
|
|
libcodes "github.com/wangjia/codes"
|
|
)
|
|
|
|
// GenerateCode generates a single random activation code in canonical Crockford
|
|
// Base32 form (15 random data chars + 1 check char = 16 chars total).
|
|
// It delegates to the shared library, which uses crypto/rand.
|
|
func GenerateCode() (string, error) {
|
|
return libcodes.GenerateCode()
|
|
}
|
|
|
|
// Canonicalize converts an activation-code string into canonical form:
|
|
// uppercase, with I/L→1 and O→0 substitutions applied.
|
|
// Hyphens and spaces are stripped before validation.
|
|
// Returns an error if the string contains characters outside the normalised
|
|
// Crockford alphabet, if the length is not exactly 16, or if the check character
|
|
// is incorrect.
|
|
func Canonicalize(code string) (string, error) {
|
|
return libcodes.Canonicalize(code)
|
|
}
|
|
|
|
// Hash returns the hex-encoded SHA-256 of the canonical plaintext code.
|
|
// This is the value stored in the database; the plaintext is never stored.
|
|
func Hash(canonical string) string {
|
|
return libcodes.Hash(canonical)
|
|
}
|
|
|
|
// ErrDuplicate is returned by the batch generator when a generated code
|
|
// already exists in the database (hash collision). The caller should retry.
|
|
// Aliased to the same sentinel as the shared library so errors.Is succeeds
|
|
// across both layers.
|
|
var ErrDuplicate = libcodes.ErrDuplicate
|