$productId, 'unit_amount' => $amountCents, 'currency' => strtolower($currency), 'interval' => $interval, 'metadata' => $metadata, ]); } /** The key for a Product, for the same reason and against the same trap. */ public static function forProduct(?string $key, string $name, array $metadata): ?string { return self::with($key, ['name' => $name, 'metadata' => $metadata]); } /** * Eight hex characters over the parameters, canonically ordered. * * Ordered, because PHP keeps insertion order and two callers writing the * same metadata in a different order would otherwise send two keys for one * call — which would mint two Prices and be a worse bug than the one this * closes. */ public static function fingerprint(array $parameters): string { return substr(sha1(self::canonical($parameters)), 0, 8); } private static function with(?string $key, array $parameters): ?string { // Null stays null: a caller who sends no key wants no replay, and // inventing one here would change what the call means. return $key === null ? null : $key.'-'.self::fingerprint($parameters); } private static function canonical(array $parameters): string { ksort($parameters); foreach ($parameters as $name => $value) { $parameters[$name] = is_array($value) ? self::canonical($value) : (string) $value; } return json_encode($parameters, JSON_THROW_ON_ERROR); } }