runStartedSpend<T> function
Future<StartedSpend<T> >
runStartedSpend<T>({
- required Future<
T> authorize(- SpendStart<
T> start
- SpendStart<
- required bool precedesPersistence(
- Object error
- required String atMostOnceMessage,
- void onEntered()?,
Run one spend through the host's authorizer and say what became of it.
authorize calls the authorizer; its closure runs its own fences, then
returns start(() => session.send(...)). start enforces AT MOST ONCE (a
second run throws StateError with atMostOnceMessage — a second proposal
token, or on the queue a second payment), runs beforeEnter, marks the
spend started (and calls onEntered), and records the SDK call's result or
its own throw as they happen.
Never throws: every error comes back inside the verdict.
Implementation
Future<StartedSpend<T>> runStartedSpend<T>({
required Future<T> Function(SpendStart<T> start) authorize,
required bool Function(Object error) precedesPersistence,
required String atMostOnceMessage,
void Function()? onEntered,
}) async {
var entered = false;
var hasLanded = false;
T? landed;
Object? sdkThrew;
Future<T> start(
Future<T> Function() sdkCall, {
void Function()? beforeEnter,
}) {
if (entered) throw StateError(atMostOnceMessage);
beforeEnter?.call();
// Past this line a transaction may exist no matter what happens next.
entered = true;
onEntered?.call();
return sdkCall().then(
(value) {
landed = value;
hasLanded = true;
return value;
},
onError: (Object error, StackTrace stack) {
sdkThrew = error;
Error.throwWithStackTrace(error, stack);
},
);
}
try {
await authorize(start);
} catch (error) {
if (hasLanded) return SpendLanded<T>(landed as T, errorAfter: error);
if (!entered) return SpendNotStarted<T>(error);
if (identical(error, sdkThrew) && precedesPersistence(error)) {
return SpendRefusedBeforePersist<T>(error);
}
return SpendAnswerLost<T>(error);
}
if (hasLanded) return SpendLanded<T>(landed as T);
if (entered) return SpendAnswerLost<T>(null);
return SpendNotStarted<T>(null);
}