Add docs to /v1/donations/redeem-receipt
This commit is contained in:
parent
9a1da23bdb
commit
626a7fdad7
|
@ -6,6 +6,8 @@
|
||||||
package org.whispersystems.textsecuregcm.controllers;
|
package org.whispersystems.textsecuregcm.controllers;
|
||||||
|
|
||||||
import io.dropwizard.auth.Auth;
|
import io.dropwizard.auth.Auth;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
@ -70,21 +72,40 @@ public class DonationController {
|
||||||
@Path("/redeem-receipt")
|
@Path("/redeem-receipt")
|
||||||
@Consumes(MediaType.APPLICATION_JSON)
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
@Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN})
|
@Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN})
|
||||||
|
@Operation(
|
||||||
|
summary = "Redeem receipt",
|
||||||
|
description = """
|
||||||
|
Redeem a receipt acquired from /v1/subscription/{subscriberId}/receipt_credentials to add a badge to the
|
||||||
|
account. After successful redemption, profile responses will include the corresponding badge (if configured as
|
||||||
|
visible) until the expiration time on the receipt.
|
||||||
|
""")
|
||||||
|
@ApiResponse(responseCode = "200", description = "The receipt was redeemed")
|
||||||
|
@ApiResponse(responseCode = "400", description = """
|
||||||
|
The provided presentation or receipt was invalid, or the receipt was already redeemed for a different account. A
|
||||||
|
specific error message suitable for logging will be included as text/plain body
|
||||||
|
""")
|
||||||
|
@ApiResponse(responseCode = "429", description = "Rate limited.")
|
||||||
public CompletionStage<Response> redeemReceipt(
|
public CompletionStage<Response> redeemReceipt(
|
||||||
@Mutable @Auth final AuthenticatedDevice auth,
|
@Mutable @Auth final AuthenticatedDevice auth,
|
||||||
@NotNull @Valid final RedeemReceiptRequest request) {
|
@NotNull @Valid final RedeemReceiptRequest request) {
|
||||||
return CompletableFuture.supplyAsync(() -> {
|
return CompletableFuture.supplyAsync(() -> {
|
||||||
ReceiptCredentialPresentation receiptCredentialPresentation;
|
ReceiptCredentialPresentation receiptCredentialPresentation;
|
||||||
try {
|
try {
|
||||||
receiptCredentialPresentation = receiptCredentialPresentationFactory.build(
|
receiptCredentialPresentation = receiptCredentialPresentationFactory
|
||||||
request.getReceiptCredentialPresentation());
|
.build(request.getReceiptCredentialPresentation());
|
||||||
} catch (InvalidInputException e) {
|
} catch (InvalidInputException e) {
|
||||||
return CompletableFuture.completedFuture(Response.status(Status.BAD_REQUEST).entity("invalid receipt credential presentation").type(MediaType.TEXT_PLAIN_TYPE).build());
|
return CompletableFuture.completedFuture(Response.status(Status.BAD_REQUEST)
|
||||||
|
.entity("invalid receipt credential presentation")
|
||||||
|
.type(MediaType.TEXT_PLAIN_TYPE)
|
||||||
|
.build());
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
serverZkReceiptOperations.verifyReceiptCredentialPresentation(receiptCredentialPresentation);
|
serverZkReceiptOperations.verifyReceiptCredentialPresentation(receiptCredentialPresentation);
|
||||||
} catch (VerificationFailedException e) {
|
} catch (VerificationFailedException e) {
|
||||||
return CompletableFuture.completedFuture(Response.status(Status.BAD_REQUEST).entity("receipt credential presentation verification failed").type(MediaType.TEXT_PLAIN_TYPE).build());
|
return CompletableFuture.completedFuture(Response.status(Status.BAD_REQUEST)
|
||||||
|
.entity("receipt credential presentation verification failed")
|
||||||
|
.type(MediaType.TEXT_PLAIN_TYPE)
|
||||||
|
.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
final ReceiptSerial receiptSerial = receiptCredentialPresentation.getReceiptSerial();
|
final ReceiptSerial receiptSerial = receiptCredentialPresentation.getReceiptSerial();
|
||||||
|
@ -92,16 +113,19 @@ public class DonationController {
|
||||||
final long receiptLevel = receiptCredentialPresentation.getReceiptLevel();
|
final long receiptLevel = receiptCredentialPresentation.getReceiptLevel();
|
||||||
final String badgeId = badgesConfiguration.getReceiptLevels().get(receiptLevel);
|
final String badgeId = badgesConfiguration.getReceiptLevels().get(receiptLevel);
|
||||||
if (badgeId == null) {
|
if (badgeId == null) {
|
||||||
return CompletableFuture.completedFuture(Response.serverError().entity("server does not recognize the requested receipt level").type(MediaType.TEXT_PLAIN_TYPE).build());
|
return CompletableFuture.completedFuture(Response.serverError()
|
||||||
|
.entity("server does not recognize the requested receipt level")
|
||||||
|
.type(MediaType.TEXT_PLAIN_TYPE)
|
||||||
|
.build());
|
||||||
}
|
}
|
||||||
return redeemedReceiptsManager.put(
|
return redeemedReceiptsManager.put(
|
||||||
receiptSerial, receiptExpiration.getEpochSecond(), receiptLevel, auth.getAccount().getUuid())
|
receiptSerial, receiptExpiration.getEpochSecond(), receiptLevel, auth.getAccount().getUuid())
|
||||||
.thenCompose(receiptMatched -> {
|
.thenCompose(receiptMatched -> {
|
||||||
if (!receiptMatched) {
|
if (!receiptMatched) {
|
||||||
|
return CompletableFuture.completedFuture(Response.status(Status.BAD_REQUEST)
|
||||||
return CompletableFuture.completedFuture(
|
.entity("receipt serial is already redeemed")
|
||||||
Response.status(Status.BAD_REQUEST).entity("receipt serial is already redeemed")
|
.type(MediaType.TEXT_PLAIN_TYPE)
|
||||||
.type(MediaType.TEXT_PLAIN_TYPE).build());
|
.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
return accountsManager.updateAsync(auth.getAccount(), a -> {
|
return accountsManager.updateAsync(auth.getAccount(), a -> {
|
||||||
|
@ -111,7 +135,7 @@ public class DonationController {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.thenApply(ignored -> Response.ok().build());
|
.thenApply(ignored -> Response.ok().build());
|
||||||
});
|
});
|
||||||
}).thenCompose(Function.identity());
|
}).thenCompose(Function.identity());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,12 +7,16 @@ package org.whispersystems.textsecuregcm.entities;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import jakarta.validation.constraints.NotEmpty;
|
import jakarta.validation.constraints.NotEmpty;
|
||||||
|
|
||||||
public class RedeemReceiptRequest {
|
public class RedeemReceiptRequest {
|
||||||
|
|
||||||
|
@Schema(description = "Presentation of a ZK receipt encoded in standard padded base64", implementation = String.class)
|
||||||
private final byte[] receiptCredentialPresentation;
|
private final byte[] receiptCredentialPresentation;
|
||||||
|
@Schema(description = "If true, the corresponding badge should be visible on the profile")
|
||||||
private final boolean visible;
|
private final boolean visible;
|
||||||
|
@Schema(description = "if true, and the new badge is visible, it should be the primary badge on the profile")
|
||||||
private final boolean primary;
|
private final boolean primary;
|
||||||
|
|
||||||
@JsonCreator
|
@JsonCreator
|
||||||
|
|
Loading…
Reference in New Issue