            token = match.group(1)
            if token == "prefix":
                return prefix
            if token == "date":
                return datetime.utcnow().strftime("%Y%m%d")
            if token.startswith("R"):  # e.g. R4, R5
                try:
                    n = int(token[1:])
                except ValueError:
                    return token
                return rnd(upper, n)
            if token.startswith("r"):
                try:
                    n = int(token[1:])
                except ValueError:
                    return token
                return rnd(lower, n)
            if token.startswith("d"):
                try:
                    n = int(token[1:])
                except ValueError:
                    return token
                return rnd(digits_only, n)
            if token == "seq4":
                return rnd(digits_only, 4)
            if token.startswith("hash"):
                n = int(token[4:] or 8)
                return secrets.token_hex(n)
            return match.group(0)

        return re.sub(r"\{([A-Za-z0-9_]+)(\d*)\}", repl, fmt)

    def generate_amount(self) -> float:
        lo = float(self._cfg("amount_min", 300.0))
        hi = float(self._cfg("amount_max", 1500.0))
        if hi < lo:
            lo, hi = hi, lo
        return round(self.rng.uniform(lo, hi), 2)

    def generate_shipping(self) -> float:
        lo = float(self._cfg("shipping_min", 5.0))
