Introduction to entitlement revocation
The Extend Override and Events handler is currently available for Closed Beta partners only. If you're interested in becoming a Closed Beta partner, please submit a ticket to apply.
Overview
AccelByte Gaming Services (AGS) has the capability to provide custom logic for handling the revocation of player assets with Extend. In this guide, we will present the contract of the customization with example code on how to implement a revoke function that will send the necessary information to identify the user, the object, and the quantity to be revoked.
There's only one function on the contract, seen in the snippet below which is the unary function 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'll generate some custom responses for the three types of entries that are currently supported; ITEM
, ENTITLEMENT
, and CURRENCY
.
Code Example:
- Go
- Python
The Revoke
method will run the correct function based on the revocation entry type
passed in.
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
}
The Revoke
method will run the correct function based on the revokeEntryType
passed in.
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
You will be able to modify the logic for those functions under revocation.go
.
When the ITEM
entry type is passed in:
- Go
- 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
}
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,
)
When CURRENCY
entry is passed in:
- Go
- 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
}
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,
)
When the ENTITLEMENT
entry is passed in:
- Go
- 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
}
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,
)