Introduction to entitlement revocation
Overview
This article walks you through how to modify the Extend Override app template for entitlement revocation and transform it into your own app that fits your requirements.
Contract functions
There is only one function on the contract as shown in the snippet below, which is a unary function called Revoke
.
service Revocation {
/**
Revoke
Currently, only Third-Party DLC Refund and Refund Order will trigger this grpc revocation.
*/
rpc Revoke(RevokeRequest) returns (RevokeResponse);
}
Revoke
Revoke
allows for the handling of revocation requests triggered by specific events within a gaming or virtual economy system. It is used for implementing the logic to manage entitlements and ensures the proper handling of refunds or revocation orders.
In this example, we will generate some custom responses for the three types of entries that are currently supported: ITEM
, ENTITLEMENT
, and CURRENCY
.
The top-level revoke function gets the revoke entry type from the request and executes the corresponding function.
- Go
- C#
- Java
- Python
In the app, the following function can be found in pkg/service/revocation_service.go
.
func (s *RevocationServiceServer) Revoke(ctx context.Context, req *pb.RevokeRequest) (*pb.RevokeResponse, error) {
revocationEntryType := revocation.RevokeEntryType(req.GetRevokeEntryType())
revocationObj, err := revocation.GetRevocation(revocationEntryType)
if err != nil {
return &pb.RevokeResponse{
Status: revocation.StatusFail,
Reason: err.Error(),
}, nil
}
revocationResp, err := revocationObj.Revoke(req.GetNamespace(), req.GetUserId(), req.GetQuantity(), req)
if err != nil {
return &pb.RevokeResponse{
Status: revocation.StatusFail,
Reason: err.Error(),
}, nil
}
return revocationResp, nil
}
In the app, the following function can be found in src/AccelByte.PluginArch.Revocation.Demo.Server/Services/RevocationFunctionService.cs
.
public override Task<RevokeResponse> Revoke(RevokeRequest request, ServerCallContext context)
{
var response = new RevokeResponse();
response.Status = "SUCCESS";
string revokeType = request.RevokeEntryType.Trim().ToUpper();
if (revokeType == "ITEM")
{
...
}
else if (revokeType == "CURRENCY")
{
...
}
else if (revokeType == "ENTITLEMENT")
{
...
}
else
{
response.Status = "FAIL";
response.Reason = $"Revocation type [{revokeType}] is not supported.";
}
return Task.FromResult(response);
}
In the app, the following function can be found in src/main/java/net/accelbyte/service/RevocationServiceImplementation.java
.
@Override
public void revoke(RevokeRequest request, StreamObserver<RevokeResponse> responseObserver) {
RevokeResponse response;
try {
String namespace = request.getNamespace();
String userId = request.getUserId();
int quantity = request.getQuantity();
RevokeEntryType revokeEntryType = RevokeEntryType.valueOf(request.getRevokeEntryType().toUpperCase());
Revocation revocation = Revocations.getRevocation(revokeEntryType);
response = revocation.revoke(namespace, userId, quantity, request);
} catch (Throwable ex) {
response = RevokeResponse.newBuilder()
.setStatus(RevocationStatus.FAIL.name())
.setReason(ex.getMessage())
.build();
}
responseObserver.onNext(response);
responseObserver.onCompleted();
}
In the app, the following function can be found in src/master/src/app/services/revocation_service.py
.
async def Revoke(self, request: RevokeRequest, context):
self.log_payload(f'{self.Revoke.__name__} request: %s', request)
response : RevokeResponse
try:
namespace = request.namespace
userId = request.userId
quantity = request.quantity
revoke_entry_type = RevokeEntryType[request.revokeEntryType.upper()]
revocation = Revocations().get_revocation(revoke_entry_type)
response = revocation.revoke(namespace, userId, quantity, request)
except Exception as e:
response = RevokeResponse(
reason = f"Revocation method {str(e)} not supported",
status = RevocationStatus.FAIL.name,
)
self.log_payload(f'{self.Revoke.__name__} response: %s', response)
return response
If the revoke entry type is ITEM
, the following function is executed.
- Go
- C#
- Java
- Python
func (r *ItemRevocation) Revoke(namespace string, userId string, quantity int32, request *pb.RevokeRequest) (*pb.RevokeResponse, error) {
item := request.GetItem()
customRevocation := map[string]string{}
customRevocation["namespace"] = namespace
customRevocation["userId"] = userId
customRevocation["quantity"] = fmt.Sprintf("%d", quantity)
customRevocation["itemId"] = item.GetItemId()
customRevocation["sku"] = item.GetItemSku()
customRevocation["itemType"] = item.GetItemType()
customRevocation["useCount"] = fmt.Sprintf("%d", item.GetUseCount())
customRevocation["entitlementType"] = item.GetEntitlementType()
return &pb.RevokeResponse{
Status: StatusSuccess,
CustomRevocation: customRevocation,
}, nil
}
response.CustomRevocation.Add(new Dictionary<string, string>()
{
{ "namespace", request.Namespace },
{ "userId", request.UserId },
{ "quantity", request.Quantity.ToString() },
{ "itemId", request.Item.ItemId },
{ "sku", request.Item.ItemSku },
{ "itemType", request.Item.ItemType },
{ "useCount", request.Item.UseCount.ToString() },
{ "entitlementType", request.Item.EntitlementType }
});
public class ItemRevocation implements Revocation {
@Override
public RevokeResponse revoke(String namespace, String userId, int quantity, RevokeRequest request) {
Map<String, String> customRevocation = new HashMap<>();
// Execute your logic; this is for demo purposes only
RevokeItemObject item = request.getItem();
customRevocation.put("namespace", namespace);
customRevocation.put("userId", userId);
customRevocation.put("quantity", String.valueOf(quantity));
customRevocation.put("itemId", item.getItemId());
customRevocation.put("sku", item.getItemSku());
customRevocation.put("itemType", item.getItemType());
customRevocation.put("useCount", String.valueOf(item.getUseCount()));
customRevocation.put("entitlementType", item.getEntitlementType());
return RevokeResponse.newBuilder()
.putAllCustomRevocation(customRevocation)
.setStatus(RevocationStatus.SUCCESS.name()).build();
}
}
class ItemRevocation(Revocation):
def __init__(self) -> None:
self.custom_revocation = dict()
super().__init__()
def revoke(self, namespace, userId, quantity, request):
item = request.item
self.custom_revocation["namespace"] = namespace
self.custom_revocation["userId"] = userId
self.custom_revocation["quantity"] = str(quantity)
self.custom_revocation["sku"] = item.itemSku
self.custom_revocation["itemType"] = item.itemType
self.custom_revocation["useCount"] = str(item.useCount)
self.custom_revocation["entitlementType"] = item.entitlementType
return RevokeResponse(
customRevocation = self.custom_revocation,
status = RevocationStatus.SUCCESS.name,
)
If the revoke entry type is CURRENCY
, the following function is executed.
- Go
- C#
- Java
- Python
func (r *CurrencyRevocation) Revoke(namespace string, userId string, quantity int32, request *pb.RevokeRequest) (*pb.RevokeResponse, error) {
currency := request.GetCurrency()
customRevocation := map[string]string{}
customRevocation["namespace"] = namespace
customRevocation["userId"] = userId
customRevocation["quantity"] = fmt.Sprintf("%d", quantity)
customRevocation["currencyNamespace"] = currency.GetNamespace()
customRevocation["currencyCode"] = currency.GetCurrencyCode()
customRevocation["balanceOrigin"] = currency.GetBalanceOrigin()
return &pb.RevokeResponse{
Status: StatusSuccess,
CustomRevocation: customRevocation,
}, nil
}
response.CustomRevocation.Add(new Dictionary<string, string>()
{
{ "namespace", request.Namespace },
{ "userId", request.UserId },
{ "quantity", request.Quantity.ToString() },
{ "currencyNamespace", request.Currency.Namespace },
{ "currencyCode", request.Currency.CurrencyCode },
{ "balanceOrigin", request.Currency.BalanceOrigin }
});
public class CurrencyRevocation implements Revocation {
@Override
public RevokeResponse revoke(String namespace, String userId, int quantity, RevokeRequest request) {
Map<String, String> customRevocation = new HashMap<>();
// Execute your logic; this is for demo purposes only
RevokeCurrencyObject currency = request.getCurrency();
customRevocation.put("namespace", namespace);
customRevocation.put("userId", userId);
customRevocation.put("quantity", String.valueOf(quantity));
customRevocation.put("currencyNamespace", currency.getNamespace());
customRevocation.put("currencyCode", currency.getCurrencyCode());
customRevocation.put("balanceOrigin", currency.getBalanceOrigin());
return RevokeResponse.newBuilder()
.putAllCustomRevocation(customRevocation)
.setStatus(RevocationStatus.SUCCESS.name()).build();
}
}
class CurrencyRevocation(Revocation):
def __init__(self) -> None:
self.custom_revocation = dict()
super().__init__()
def revoke(self, namespace, userId, quantity, request):
currency = request.currency
self.custom_revocation["namespace"] = namespace
self.custom_revocation["userId"] = userId
self.custom_revocation["quantity"] = str(quantity)
self.custom_revocation["currencyNamespace"] = currency.namespace
self.custom_revocation["currencyCode"] = currency.currencyCode
self.custom_revocation["balanceOrigin"] = currency.balanceOrigin
return RevokeResponse(
customRevocation = self.custom_revocation,
status = RevocationStatus.SUCCESS.name,
)
If the revoke entry type is ENTITLEMENT
, the following function is executed.
- Go
- C#
- Java
- Python
func (r *EntitlementRevocation) Revoke(namespace string, userId string, quantity int32, request *pb.RevokeRequest) (*pb.RevokeResponse, error) {
entitlement := request.GetEntitlement()
customRevocation := map[string]string{}
customRevocation["namespace"] = namespace
customRevocation["userId"] = userId
customRevocation["quantity"] = fmt.Sprintf("%d", quantity)
customRevocation["entitlementId"] = entitlement.GetEntitlementId()
customRevocation["itemId"] = entitlement.GetItemId()
customRevocation["sku"] = entitlement.GetSku()
return &pb.RevokeResponse{
Status: StatusSuccess,
CustomRevocation: customRevocation,
}, nil
}
response.CustomRevocation.Add(new Dictionary<string, string>()
{
{ "namespace", request.Namespace },
{ "userId", request.UserId },
{ "quantity", request.Quantity.ToString() },
{ "entitlementId", request.Entitlement.EntitlementId },
{ "itemId", request.Entitlement.ItemId },
{ "sku", request.Entitlement.Sku },
});
public class EntitlementRevocation implements Revocation {
@Override
public RevokeResponse revoke(String namespace, String userId, int quantity, RevokeRequest request) {
Map<String, String> customRevocation = new HashMap<>();
// execute your logic, this is for demo only
RevokeEntitlementObject entitlement = request.getEntitlement();
customRevocation.put("namespace", namespace);
customRevocation.put("userId", userId);
customRevocation.put("quantity", String.valueOf(quantity));
customRevocation.put("entitlementId", entitlement.getEntitlementId());
customRevocation.put("itemId", entitlement.getItemId());
customRevocation.put("sku", entitlement.getSku());
return RevokeResponse.newBuilder()
.putAllCustomRevocation(customRevocation)
.setStatus(RevocationStatus.SUCCESS.name()).build();
}
}
class EntitlementRevocation(Revocation):
def __init__(self) -> None:
self.custom_revocation = dict()
super().__init__()
def revoke(self, namespace, userId, quantity, request):
entitlement = request.entitlement
self.custom_revocation["namespace"] = namespace
self.custom_revocation["userId"] = userId
self.custom_revocation["quantity"] = str(quantity)
self.custom_revocation["entitlementId"] = entitlement.entitlementId
self.custom_revocation["itemId"] = entitlement.itemId
self.custom_revocation["sku"] = entitlement.sku
return RevokeResponse(
customRevocation = self.custom_revocation,
status = RevocationStatus.SUCCESS.name,
)