Use latest invoice on subscription to generate receipts

This commit is contained in:
Ehren Kret 2021-11-19 11:25:38 -06:00
parent 6547d5ebf3
commit 279b0a51d9
2 changed files with 53 additions and 38 deletions

View File

@ -21,7 +21,6 @@ import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Base64;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@ -743,7 +742,7 @@ public class SubscriptionController {
.thenApply(this::requireRecordFromGetResult)
.thenCompose(record -> {
if (record.subscriptionId == null) {
return CompletableFuture.completedFuture(Response.noContent().build());
return CompletableFuture.completedFuture(Response.status(Status.NOT_FOUND).build());
}
ReceiptCredentialRequest receiptCredentialRequest;
try {
@ -751,29 +750,20 @@ public class SubscriptionController {
} catch (InvalidInputException e) {
throw new BadRequestException("invalid receipt credential request", e);
}
return stripeManager.getPaidInvoicesForSubscription(record.subscriptionId, requestData.now)
.thenCompose(invoices -> checkNextInvoice(invoices.iterator(), record.subscriptionId))
.thenCompose(receipt -> {
if (receipt == null) {
return CompletableFuture.completedFuture(null);
}
return issuedReceiptsManager.recordIssuance(
receipt.getInvoiceLineItemId(), receiptCredentialRequest, requestData.now)
.thenApply(unused -> receipt);
})
return stripeManager.getLatestInvoiceForSubscription(record.subscriptionId)
.thenCompose(invoice -> convertInvoiceToReceipt(invoice, record.subscriptionId))
.thenCompose(receipt -> issuedReceiptsManager.recordIssuance(
receipt.getInvoiceLineItemId(), receiptCredentialRequest, requestData.now)
.thenApply(unused -> receipt))
.thenApply(receipt -> {
if (receipt == null) {
return Response.noContent().build();
} else {
ReceiptCredentialResponse receiptCredentialResponse;
try {
receiptCredentialResponse = zkReceiptOperations.issueReceiptCredential(
receiptCredentialRequest, receipt.getExpiration().getEpochSecond(), receipt.getLevel());
} catch (VerificationFailedException e) {
throw new BadRequestException("receipt credential request failed verification", e);
}
return Response.ok(new GetReceiptCredentialsResponse(receiptCredentialResponse.serialize())).build();
ReceiptCredentialResponse receiptCredentialResponse;
try {
receiptCredentialResponse = zkReceiptOperations.issueReceiptCredential(
receiptCredentialRequest, receipt.getExpiration().getEpochSecond(), receipt.getLevel());
} catch (VerificationFailedException e) {
throw new BadRequestException("receipt credential request failed verification", e);
}
return Response.ok(new GetReceiptCredentialsResponse(receiptCredentialResponse.serialize())).build();
});
});
}
@ -803,35 +793,46 @@ public class SubscriptionController {
}
}
private CompletableFuture<Receipt> checkNextInvoice(Iterator<Invoice> invoiceIterator, String subscriptionId) {
if (!invoiceIterator.hasNext()) {
return null;
private CompletableFuture<Receipt> convertInvoiceToReceipt(Invoice latestSubscriptionInvoice, String subscriptionId) {
if (latestSubscriptionInvoice == null) {
throw new WebApplicationException(Status.NO_CONTENT);
}
if (StringUtils.equalsIgnoreCase("open", latestSubscriptionInvoice.getStatus())) {
throw new WebApplicationException(Status.NO_CONTENT);
}
if (!StringUtils.equalsIgnoreCase("paid", latestSubscriptionInvoice.getStatus())) {
throw new WebApplicationException(Status.PAYMENT_REQUIRED);
}
Invoice invoice = invoiceIterator.next();
return stripeManager.getInvoiceLineItemsForInvoice(invoice).thenCompose(invoiceLineItems -> {
return stripeManager.getInvoiceLineItemsForInvoice(latestSubscriptionInvoice).thenCompose(invoiceLineItems -> {
Collection<InvoiceLineItem> subscriptionLineItems = invoiceLineItems.stream()
.filter(invoiceLineItem -> Objects.equals("subscription", invoiceLineItem.getType()))
.collect(Collectors.toList());
if (subscriptionLineItems.isEmpty()) {
return checkNextInvoice(invoiceIterator, subscriptionId);
throw new IllegalStateException("latest subscription invoice has no subscription line items; subscriptionId="
+ subscriptionId + "; invoiceId=" + latestSubscriptionInvoice.getId());
}
if (subscriptionLineItems.size() > 1) {
throw new IllegalStateException("invoice has more than one subscription; subscriptionId=" + subscriptionId
+ "; count=" + subscriptionLineItems.size());
throw new IllegalStateException(
"latest subscription invoice has too many subscription line items; subscriptionId=" + subscriptionId
+ "; invoiceId=" + latestSubscriptionInvoice.getId() + "; count=" + subscriptionLineItems.size());
}
InvoiceLineItem subscriptionLineItem = subscriptionLineItems.stream().findAny().get();
return stripeManager.getProductForPrice(subscriptionLineItem.getPrice().getId()).thenApply(product -> new Receipt(
Instant.ofEpochSecond(subscriptionLineItem.getPeriod().getEnd())
.plus(subscriptionConfiguration.getBadgeGracePeriod())
.truncatedTo(ChronoUnit.DAYS)
.plus(1, ChronoUnit.DAYS),
stripeManager.getLevelForProduct(product),
subscriptionLineItem.getId()));
return getReceiptForSubscriptionInvoiceLineItem(subscriptionLineItem);
});
}
private CompletableFuture<Receipt> getReceiptForSubscriptionInvoiceLineItem(InvoiceLineItem subscriptionLineItem) {
return stripeManager.getProductForPrice(subscriptionLineItem.getPrice().getId()).thenApply(product -> new Receipt(
Instant.ofEpochSecond(subscriptionLineItem.getPeriod().getEnd())
.plus(subscriptionConfiguration.getBadgeGracePeriod())
.truncatedTo(ChronoUnit.DAYS)
.plus(1, ChronoUnit.DAYS),
stripeManager.getLevelForProduct(product),
subscriptionLineItem.getId()));
}
private SubscriptionManager.Record requireRecordFromGetResult(SubscriptionManager.GetResult getResult) {
if (getResult == GetResult.PASSWORD_MISMATCH) {
throw new ForbiddenException("subscriberId mismatch");

View File

@ -29,6 +29,7 @@ import com.stripe.param.SetupIntentCreateParams;
import com.stripe.param.SubscriptionCancelParams;
import com.stripe.param.SubscriptionCreateParams;
import com.stripe.param.SubscriptionListParams;
import com.stripe.param.SubscriptionRetrieveParams;
import com.stripe.param.SubscriptionUpdateParams;
import com.stripe.param.SubscriptionUpdateParams.BillingCycleAnchor;
import com.stripe.param.SubscriptionUpdateParams.ProrationBehavior;
@ -345,6 +346,19 @@ public class StripeManager {
}, executor);
}
public CompletableFuture<Invoice> getLatestInvoiceForSubscription(String subscriptionId) {
return CompletableFuture.supplyAsync(() -> {
SubscriptionRetrieveParams params = SubscriptionRetrieveParams.builder()
.addExpand("latest_invoice")
.build();
try {
return Subscription.retrieve(subscriptionId, params, commonOptions()).getLatestInvoiceObject();
} catch (StripeException e) {
throw new CompletionException(e);
}
}, executor);
}
public CompletableFuture<Collection<InvoiceLineItem>> getInvoiceLineItemsForInvoice(Invoice invoice) {
return CompletableFuture.supplyAsync(
() -> Lists.newArrayList(invoice.getLines().autoPagingIterable(null, commonOptions())), executor);