getMessage(), $exception ); } return self::to_error( __( 'An unexpected error occurred while contacting PostNL. Check the PostNL logs for details.', 'postnl-for-woocommerce' ), $exception ); } /** * Whether a retry could plausibly succeed. * * RetryableExceptionInterface only declares the capability, so the predicate * has to be asked rather than the type: ServerException implements it for * every 5xx but reports false for permanent ones such as 501. The SDK's own * retry policy gates on isRetryable() for the same reason. * * RetryExhaustedException does not implement the interface, so it is checked * separately; the policy only ever raises it after retryable failures. * * @param \Throwable $exception Original SDK exception being converted. * @return bool */ private static function is_transient( \Throwable $exception ): bool { if ( $exception instanceof RetryExhaustedException ) { return true; } return $exception instanceof RetryableExceptionInterface && $exception->isRetryable(); } /** * Build the converted error, appending the traceId and preserving both the * status code and the original exception as the cause. * * @param string $message Human-readable, already-translated message. * @param \Throwable $exception Original SDK exception being converted. * @return \Exception */ private static function to_error( string $message, \Throwable $exception ): \Exception { return new \Exception( $message . self::trace_suffix( $exception ), self::status_code( $exception ), $exception ); } /** * Flatten a ValidationException's field errors into "field: message" pairs. * * Falls back to the exception's own (already-cleaned) message when PostNL * returned a 400/422 without any structured field errors. * * @param ValidationException $exception Validation exception to describe. * @return string */ private static function validation_message( ValidationException $exception ): string { $parts = array(); foreach ( $exception->getFieldErrors() as $field_error ) { $parts[] = sprintf( '%1$s: %2$s', $field_error->field, $field_error->message ); } if ( empty( $parts ) ) { return $exception->getMessage(); } return implode( '; ', $parts ); } /** * The PostNL correlation suffix, present only on HTTP exceptions that carry a traceId. * * @param \Throwable $exception Original SDK exception being converted. * @return string Empty string when no traceId is available. */ private static function trace_suffix( \Throwable $exception ): string { if ( $exception instanceof HttpSdkException ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Third-party SDK DTO property. $trace_id = $exception->problemDetails->traceId; if ( null !== $trace_id && '' !== $trace_id ) { return sprintf( ' (traceId: %s)', $trace_id ); } } return ''; } /** * The status code to preserve on the converted error. * * HTTP exceptions report their status via getCode(); pre-request failures * (auth, transport) report 0, which is preserved as-is. * * @param \Throwable $exception Original SDK exception being converted. * @return int */ private static function status_code( \Throwable $exception ): int { return (int) $exception->getCode(); } }