$data * @param list $skips Additional top-level/nested keys to exclude beyond sign/sign_type. */ public static function joinKeyVal(array $data, array $skips = []): string { $skips = [...$skips, 'sign', 'sign_type']; $fields = []; self::collect($data, $skips, $fields); usort($fields, fn (array $a, array $b): int => strcmp($a['key'], $b['key'])); $pairs = []; foreach ($fields as $field) { if ($field['val'] !== null && trim((string) $field['val']) !== '') { $pairs[] = $field['key'].'='.$field['val']; } } return implode('&', $pairs); } /** * @param array $data * @param list $skips */ public static function sign(array $data, string $merchantKey, array $skips = []): string { $joined = self::joinKeyVal($data, $skips); return strtoupper(hash('sha256', $joined.'&key='.$merchantKey)); } /** * @param list $skips * @param list $fields */ private static function collect(mixed $value, array $skips, array &$fields, string $key = ''): void { if (in_array($key, $skips, true)) { return; } if (is_array($value)) { foreach ($value as $subKey => $subVal) { self::collect($subVal, $skips, $fields, (string) $subKey); } return; } if ($key === '') { return; } $fields[] = ['key' => $key, 'val' => $value]; } }