$payload */ private static function string_value( array $payload, string $key ): string { $value = $payload[ $key ] ?? null; return is_scalar( $value ) ? (string) $value : ''; } /** * @param array $payload */ private static function string_or_null( array $payload, string $key ): ?string { $value = $payload[ $key ] ?? null; return is_scalar( $value ) ? (string) $value : null; } /** * The scalar with its type intact, or null when the field is absent or not scalar. * * Dates and marketplace IDs are passed through rather than cast. The REST * contract has always forwarded whatever the SaaS sent for these, and the * React side reads them as `number | string | null` to match. Money is not * one of them: it goes through float_value() or float_or_null(). * * @param array $payload */ private static function scalar_value( array $payload, string $key ): int|float|string|bool|null { $value = $payload[ $key ] ?? null; return is_scalar( $value ) ? $value : null; } /** * @param array $payload */ private static function bool_value( array $payload, string $key ): bool { return ! empty( $payload[ $key ] ); } /** * A money amount the SaaS always sends, as a float. * * Falls back to 0.0 when the field is somehow missing, which is only safe * because the SaaS never omits these. Amounts it may leave empty go through * float_or_null() instead, so a missing amount is not mistaken for zero. * * @param array $payload */ private static function float_value( array $payload, string $key ): float { return self::float_or_null( $payload, $key ) ?? 0.0; } /** * A money amount the SaaS may leave empty, as a float, or null when it did. * * The SaaS formats order-level amounts with number_format(), thousands * separator included, so 1234.5 arrives as "1,234.50" and is not numeric * as sent; the separator is stripped first. An unformatted `_raw` * twin wins when the payload carries one, which lets the SaaS move fields * off number_format() one at a time without a coordinated plugin release. * * @param array $payload */ private static function float_or_null( array $payload, string $key ): ?float { $value = $payload[ $key . '_raw' ] ?? $payload[ $key ] ?? null; if ( is_string( $value ) ) { $value = str_replace( ',', '', $value ); } return is_numeric( $value ) ? (float) $value : null; } /** * A money amount as the two-decimal string the order screen displays. * * The REST contract types money as `string | null`, and formatMoney() shows * it as given, so 39.9 has to leave here as "39.90". */ public static function money_string( ?float $amount ): ?string { return null === $amount ? null : number_format( $amount, 2, '.', '' ); } /** * The first of `$keys` the payload actually carries, as a string. * * Mirrors a `??` chain rather than a `?:` one: a key that is present but * empty wins over a later key that would have a value. Marketplaces differ * on whether they send `email_address` or `email`, and an empty * `email_address` means the marketplace withheld it, not that `email` * should be consulted. * * @param array $payload */ private static function first_string( array $payload, string ...$keys ): string { foreach ( $keys as $key ) { if ( isset( $payload[ $key ] ) ) { return is_scalar( $payload[ $key ] ) ? (string) $payload[ $key ] : ''; } } return ''; } /** * @return array */ private static function array_value( mixed $value ): array { return is_array( $value ) ? $value : []; } }