send method
Send request and return a GatewayHandoffResponse.
Throws GatewayError for any transport-level failure.
Implementation
@override
Future<GatewayHandoffResponse> send(
GatewayTransactionRequest request,
) async {
if (_closed) {
throw const GatewayError(
code: GatewayErrorCode.internalError,
message: 'DefaultGatewayClient.send called after close()',
);
}
final baseUrl = config.backendBaseUrl;
if (baseUrl == null || baseUrl.isEmpty) {
throw const GatewayError(
code: GatewayErrorCode.notConfigured,
message:
'GatewayConfig.backendBaseUrl is required in gatewayHandoff mode.',
);
}
final url = _joinUrl(baseUrl, path);
final headers = <String, String>{
'Content-Type': 'application/json',
...config.extraHeaders,
};
final tokenProvider = config.authTokenProvider;
if (tokenProvider != null) {
final token = await tokenProvider(GatewayTokenScope.transactionHandoff);
if (token != null && token.isNotEmpty) {
headers['Authorization'] = 'Bearer $token';
}
}
final body = jsonEncode(request.toJson());
final policy = config.retryPolicy;
final maxAttempts = policy.maxAttempts < 1 ? 1 : policy.maxAttempts;
var attempt = 0;
GatewayError? lastError;
while (attempt < maxAttempts) {
attempt++;
try {
final response = await transport.send(
GatewayTransportRequest(
method: 'POST',
url: url,
headers: headers,
body: body,
timeout: config.requestTimeout,
),
);
return _decode(response, attempt: attempt);
} on GatewayError catch (err) {
// A backend-error / response-invalid is non-retryable;
// only transport-level errors retry below.
if (err.code != GatewayErrorCode.transportError &&
err.code != GatewayErrorCode.transportTimeout) {
rethrow;
}
lastError = err;
} on GatewayTransportError catch (te) {
lastError = GatewayError(
code: te.isTimeout
? GatewayErrorCode.transportTimeout
: GatewayErrorCode.transportError,
message: te.message,
attempt: attempt,
recoverable: true,
);
} catch (e) {
// Wrap unknown exceptions in a transport error so
// the retry policy can apply uniformly.
lastError = GatewayError(
code: GatewayErrorCode.transportError,
message: 'Unexpected transport exception: ${e.runtimeType}',
attempt: attempt,
recoverable: true,
);
}
if (attempt < maxAttempts) {
final backoff = _backoffFor(attempt, policy);
await Future<void>.delayed(backoff);
}
}
throw lastError ??
const GatewayError(
code: GatewayErrorCode.internalError,
message: 'DefaultGatewayClient exhausted retries with no error.',
);
}