public CompletableFuture<List<Credential>> getBackupAuthCredentials(
      final Account account,
      final Instant redemptionStart,
      final Instant redemptionEnd) {

    // If the account has an expired payment, clear it before continuing
    if (hasExpiredVoucher(account)) {
      return accountsManager.updateAsync(account, a -> {
        // Re-check in case we raced with an update
        if (hasExpiredVoucher(a)) {
          a.setBackupVoucher(null);
        }
      }).thenCompose(updated -> getBackupAuthCredentials(updated, redemptionStart, redemptionEnd));
    }

    // If this account isn't allowed some level of backup access via configuration, don't continue
    final BackupLevel configuredBackupLevel = configuredBackupLevel(account).orElseThrow(() ->
        Status.PERMISSION_DENIED.withDescription("Backups not allowed on account").asRuntimeException());

    final Instant startOfDay = clock.instant().truncatedTo(ChronoUnit.DAYS);
    if (redemptionStart.isAfter(redemptionEnd) ||
        redemptionStart.isBefore(startOfDay) ||
        redemptionEnd.isAfter(startOfDay.plus(MAX_REDEMPTION_DURATION)) ||
        !redemptionStart.equals(redemptionStart.truncatedTo(ChronoUnit.DAYS)) ||
        !redemptionEnd.equals(redemptionEnd.truncatedTo(ChronoUnit.DAYS))) {

      throw Status.INVALID_ARGUMENT.withDescription("invalid redemption window").asRuntimeException();
    }

    // fetch the blinded backup-id the account should have previously committed to
    final byte[] committedBytes = account.getBackupCredentialRequest();
    if (committedBytes == null) {
      throw Status.NOT_FOUND.withDescription("No blinded backup-id has been added to the account").asRuntimeException();
    }

    try {
      // create a credential for every day in the requested period
      final BackupAuthCredentialRequest credentialReq = new BackupAuthCredentialRequest(committedBytes);
      return CompletableFuture.completedFuture(Stream
          .iterate(redemptionStart, curr -> curr.plus(Duration.ofDays(1)))
          .takeWhile(redemptionTime -> !redemptionTime.isAfter(redemptionEnd))
          .map(redemptionTime -> {
            // Check if the account has a voucher that's good for a certain receiptLevel at redemption time, otherwise
            // use the default receipt level
            final BackupLevel backupLevel = storedBackupLevel(account, redemptionTime).orElse(configuredBackupLevel);
            return new Credential(
                credentialReq.issueCredential(redemptionTime, backupLevel, serverSecretParams),
                redemptionTime);
          })
          .toList());
    } catch (InvalidInputException e) {
      throw Status.INTERNAL
          .withDescription("Could not deserialize stored request credential")
          .withCause(e)
          .asRuntimeException();
    }
  }
@Test
  void expiringBackupPayment() throws VerificationFailedException {
    clock.pin(Instant.ofEpochSecond(1));
    final Instant day0 = Instant.EPOCH;
    final Instant day4 = Instant.EPOCH.plus(Duration.ofDays(4));
    final Instant dayMax = day0.plus(BackupAuthManager.MAX_REDEMPTION_DURATION);

    final BackupAuthManager authManager = create(BackupLevel.MESSAGES, false);

    final Account account = mock(Account.class);
    when(account.getUuid()).thenReturn(aci);
    when(account.getBackupCredentialRequest()).thenReturn(backupAuthTestUtil.getRequest(backupKey, aci).serialize());
    when(account.getBackupVoucher()).thenReturn(new Account.BackupVoucher(201, day4));

    final List<BackupAuthManager.Credential> creds = authManager.getBackupAuthCredentials(account, day0, dayMax).join();
    Instant redemptionTime = day0;
    final BackupAuthCredentialRequestContext requestContext = BackupAuthCredentialRequestContext.create(backupKey, aci);
    for (int i = 0; i < creds.size(); i++) {
      // Before the expiration, credentials should have a media receipt, otherwise messages only
      final BackupLevel level = i < 5 ? BackupLevel.MEDIA : BackupLevel.MESSAGES;
      final BackupAuthManager.Credential cred = creds.get(i);
      assertThat(requestContext
          .receiveResponse(cred.credential(), redemptionTime, backupAuthTestUtil.params.getPublicParams())
          .getBackupLevel())
          .isEqualTo(level);
      assertThat(cred.redemptionTime().getEpochSecond()).isEqualTo(redemptionTime.getEpochSecond());
      redemptionTime = redemptionTime.plus(Duration.ofDays(1));
    }
  }