acceptNotification method
Applies a store server notification delivered to store's endpoint.
body and headers are the request's. Throws DVPurchaseRefused for a
signature that does not verify, having changed nothing; the endpoint
answers that with a client error. Every other outcome is an
acknowledgement, including a replay, a stale answer and an undeclared
product (DV-PURCHASE-004), because a store retries what is not
acknowledged. A failure part-way -- an acknowledgement the store did not
take -- rolls the grant and the notification's claim back and rethrows,
so the store's retry is applied rather than mistaken for a replay.
Implementation
Future<DVPurchaseResult> acceptNotification(
DVContext context,
DVStore store, {
required String body,
required Map<String, String> headers,
}) async {
final DVStoreAdapter adapter = _adapter(store);
final DVStoreNotification notification;
try {
notification = await adapter.verifyNotification(body, headers);
} on DVStoreNothingToApply {
return DVPurchaseResult._ignored;
} on DVStoreRefusal catch (refusal) {
_log.log(
'A ${store.name} notification was refused: ${refusal.reason}',
level: DVLogLevel.warn,
context: <String, Object?>{'store': store.name},
);
throw DVPurchaseRefused(refusal.reason);
}
final DVStoreTransaction transaction = notification.transaction;
if (transaction.store != store) {
throw const DVPurchaseRefused(
'the notification describes a purchase from a different store');
}
return _transactions<DVPurchaseResult>((DVContext unit) async {
if (!await ledger.claimNotification(store, notification.notificationId)) {
return DVPurchaseResult._replayed;
}
unit.compensate(
() => ledger.releaseNotification(store, notification.notificationId));
final DVPurchaseProduct? product =
_byStoreId[(store, transaction.storeProductId)];
if (product == null) {
_undeclared(store, transaction);
return DVPurchaseResult._undeclared;
}
final DVPurchaseGrant? previous =
await ledger.find(store, transaction.originalTransactionId);
if (previous == null) return DVPurchaseResult._unattributed;
if (notification.signedAt.isBefore(previous.lastEventAt)) {
return DVPurchaseResult._stale;
}
return _write(
unit,
adapter: adapter,
transaction: transaction,
product: product,
customerKey: previous.customerKey,
previous: previous,
eventAt: notification.signedAt,
notificationId: notification.notificationId,
);
});
}