Skip to main content

Use the OSS to upgrade account - In-game registration - (Unreal Engine module)

Last updated on November 13, 2024

Unwrap the Subsystem

This tutorial will show you how to upgrade a headless account to a full AccelByte Gaming Services (AGS) account using the AGS Game SDK Online Subsystem (OSS). In the Byte Wars project, there is already a game instance subsystem created named RegisterUserInGameSubsystem. This subsystem contains the completed implementation to upgrade an account. However, in this tutorial, you will use a starter version of that subsystem so you can implement the functions from scratch.

What's in the Starter Pack

To follow this tutorial, a starter subsystem class named RegisterUserInGameSubsystem_Starter has been prepared for you. This class is available in the Resources section and consists of the following files:

  • Header file: /Source/AccelByteWars/TutorialModules/Access/RegisterUserInGame/RegisterUserInGameSubsystem_Starter.h
  • CPP file: /Source/AccelByteWars/TutorialModules/Access/RegisterUserInGame/RegisterUserInGameSubsystem_Starter.cpp

The RegisterUserInGameSubsystem_Starter class also has several helpers:

  • AGS OSS UserInterface interface declaration. You will use this interface to query the new full account information.

    protected:
    // ...
    FOnlineUserAccelBytePtr GetUserInterface() const;

There is also a model file in the /Source/AccelByteWars/TutorialModules/Access/RegisterUserInGame/RegisterUserInGameModels.h file. This file contains the following helpers:

  • A delegate declaration to handle the response from the backend.

    DECLARE_DELEGATE_ThreeParams(FOnUpgradeAndVerifyAccountComplete, bool bWasSuccessful, const FString& ErrorMessage, const FAccountUserData& NewFullAccount);
    DECLARE_DELEGATE_TwoParams(FOnSendUpgradeAccountVerificationCodeComplete, bool bWasSuccesful, const FString& ErrorMessage);
  • A string constant to print logs and messages.

    #define SEND_VERIFICATION_CODE_MESSAGE NSLOCTEXT(ACCELBYTEWARS_LOCTEXT_NAMESPACE, "Upgrade Account Send Verification Code Message", "Sending verification code")
    #define RESEND_VERIFICATION_CODE_MESSAGE NSLOCTEXT(ACCELBYTEWARS_LOCTEXT_NAMESPACE, "Upgrade Account Send Verification Code Message", "Re-send Verification Code")
    #define UPGRADE_ACCOUNT_MESSAGE NSLOCTEXT(ACCELBYTEWARS_LOCTEXT_NAMESPACE, "Upgrade Account Message", "Upgrading and verifying account")
    #define EMPTY_REQUIRED_FIELDS_ERROR NSLOCTEXT(ACCELBYTEWARS_LOCTEXT_NAMESPACE, "Upgrade Account Empty Required Fields Error", "Required fields cannot be empty")
    #define EMPTY_VERIFICATION_CODE_ERROR NSLOCTEXT(ACCELBYTEWARS_LOCTEXT_NAMESPACE, "Upgrade Account Empty Verification Code Error", "Verification code cannot be empty")
    #define EMAIL_ALREADY_USED_ERROR NSLOCTEXT(ACCELBYTEWARS_LOCTEXT_NAMESPACE, "Upgrade Account Email Already Used Error", "E-mail is already used")
    #define EMAIL_INPUT_VIOLATION_ERROR NSLOCTEXT(ACCELBYTEWARS_LOCTEXT_NAMESPACE, "Upgrade Account Email Input Violation Error", "E-mail format is invalid")
    #define USERNAME_ALREADY_USED_ERROR NSLOCTEXT(ACCELBYTEWARS_LOCTEXT_NAMESPACE, "Upgrade Account Username Already Used Error", "Username is already used")
    #define USERNAME_INPUT_VIOLATION_ERROR NSLOCTEXT(ACCELBYTEWARS_LOCTEXT_NAMESPACE, "Upgrade Account Username Input Validation Error", "Username must be {0}-{1} characters and cannot contain whitespaces nor special characters")
    #define PASSWORD_INPUT_VIOLATION_ERROR NSLOCTEXT(ACCELBYTEWARS_LOCTEXT_NAMESPACE, "Upgrade Account Password Input Violation Error", "Password must be {0}-{1} characters, must contains a mix of upercase and lowercase letters, and cannot contain special characters except dash and underscore")
    #define PASSWORD_NOT_MATCH_ERROR NSLOCTEXT(ACCELBYTEWARS_LOCTEXT_NAMESPACE, "Upgrade Account Password Not Match Error", "Password does not match, retype the password")
    #define UPGRADE_ACCOUNT_UNKNOWN_ERROR NSLOCTEXT(ACCELBYTEWARS_LOCTEXT_NAMESPACE, "Upgrade Account Unknown Error", "Unknown error occurred")
    inline const FText GetUpgradeAccountErrorMessage(const EUpgradeAccountErrorTypes ErrorCode)
    {
    switch ((EUpgradeAccountErrorTypes)ErrorCode)
    {
    case EUpgradeAccountErrorTypes::EmailAlreadyUsed:
    return EMAIL_ALREADY_USED_ERROR;
    break;
    case EUpgradeAccountErrorTypes::UsernameAlreadyUsed:
    case EUpgradeAccountErrorTypes::UniqueDisplayNameAlreadyUsed:
    return USERNAME_ALREADY_USED_ERROR;
    break;
    case EUpgradeAccountErrorTypes::UsernameInputViolation:
    return FText::Format(USERNAME_INPUT_VIOLATION_ERROR, MIN_USERNAME_LEN, MAX_USERNAME_LEN);
    break;
    case EUpgradeAccountErrorTypes::PasswordInputViolation:
    return FText::Format(PASSWORD_INPUT_VIOLATION_ERROR, MIN_PASSWORD_LEN, MAX_PASSWORD_LEN);
    break;
    default:
    return UPGRADE_ACCOUNT_UNKNOWN_ERROR;
    break;
    }
    }

Implement requesting verification codes

In this section, you will implement functions to send an email verification code request.

  1. Open the RegisterUserInGameSubsystem_Starter class Header file and declare the helper variable below. This variable will be used to store the email address, preventing multiple verification code requests to the same email.

    private:
    // ...
    TSet<FString> LastVerificationCodeTargetEmails;
  2. Next, declare the following function to send an email verification code request.

    public:
    // ...
    void SendUpgradeAccountVerificationCode(
    const FString& EmailAddress,
    const bool bForceResend = false,
    const FOnSendUpgradeAccountVerificationCodeComplete& OnComplete = FOnSendUpgradeAccountVerificationCodeComplete());
  3. Now, open the RegisterUserInGameSubsystem_Starter class CPP file and define the function above. This function sends a request to the backend to send a verification code to the target email. If the bForceResend is disabled, then it won't send a new request to the same email address.

    void URegisterUserInGameSubsystem_Starter::SendUpgradeAccountVerificationCode(
    const FString& EmailAddress,
    const bool bForceResend,
    const FOnSendUpgradeAccountVerificationCodeComplete& OnComplete)
    {
    if (!bForceResend && LastVerificationCodeTargetEmails.Contains(EmailAddress))
    {
    UE_LOG_REGISTERUSERINGAME(Log, TEXT("Verification code already sent to the same e-mail."));
    OnComplete.ExecuteIfBound(true, TEXT(""));
    return;
    }

    AccelByte::FRegistry::User.SendUpgradeVerificationCode(
    EmailAddress,
    AccelByte::FVoidHandler::CreateWeakLambda(this, [this, EmailAddress, OnComplete]()
    {
    UE_LOG_REGISTERUSERINGAME(Log, TEXT("Success to send upgrade to full account verification code to the registered e-mail."));
    LastVerificationCodeTargetEmails.Add(EmailAddress);
    OnComplete.ExecuteIfBound(true, TEXT(""));
    }),
    FErrorHandler::CreateWeakLambda(this, [this, OnComplete](int32 ErrorCode, const FString& ErrorMessage)
    {
    UE_LOG_REGISTERUSERINGAME(Warning, TEXT("Failed to send upgrade to full account verification code to the registered e-mail. Error %d: %s"), ErrorCode, *ErrorMessage);
    OnComplete.ExecuteIfBound(false, GetUpgradeAccountErrorMessage((EUpgradeAccountErrorTypes)ErrorCode).ToString());
    })
    );
    }
  4. Next, find the predefined Deinitialize() function, which is called when the subsystem is deinitialized. In this function, add the code below to clear the value of the helper variable.

    void URegisterUserInGameSubsystem_Starter::Deinitialize()
    {
    Super::Deinitialize();
    LastVerificationCodeTargetEmails.Empty();
    }

Implement upgrade and verify account

In this section, you will implement functions to upgrade and verify the account using the verification code.

  1. First, create a helper function to check whether the account is already a full account or not. Open the RegisterUserInGameSubsystem_Starter class Header file and declare the following function:

    public:
    // ...
    bool IsCurrentUserIsFullAccount();
  2. Next, open the CPP file for the RegisterUserInGameSubsystem_Starter class and implement the function described above. This function checks whether the currently logged-in account is a full account by verifying if an email address is registered and verified.

    bool URegisterUserInGameSubsystem_Starter::IsCurrentUserIsFullAccount()
    {
    const bool bHasLinkedEmail = !AccelByte::FRegistry::CredentialsRef->GetUserEmailAddress().IsEmpty();
    const bool bIsEmailVerified = AccelByte::FRegistry::CredentialsRef->GetAccountUserData().EmailVerified;
    return bHasLinkedEmail && bIsEmailVerified;
    }
  3. Now, open the RegisterUserInGameSubsystem_Starter class Header file again and declare the following functions:

    public:
    // ...
    void UpgradeAndVerifyAccount(
    const int32 LocalUserNum,
    const FUniqueNetIdPtr UserId,
    const FString& Username,
    const FString& EmailAddress,
    const FString& Password,
    const FString& VerificationCode,
    const FOnUpgradeAndVerifyAccountComplete& OnComplete = FOnUpgradeAndVerifyAccountComplete());
    protected:
    void OnUpgradeAndVerifyAccountSuccess(
    const FAccountUserData& NewFullAccount,
    const int32 LocalUserNum,
    const FUniqueNetIdRef UserId,
    const FOnUpgradeAndVerifyAccountComplete OnComplete);
  4. Next, open the RegisterUserInGameSubsystem_Starter class CPP file and define the UpgradeAndVerifyAccount() function. This function sends a request to upgrade the account by using the verification code received via email. Once the request completes, the callback will be handled by the OnUpgradeAndVerifyAccountSuccess() function.

    void URegisterUserInGameSubsystem_Starter::UpgradeAndVerifyAccount(
    const int32 LocalUserNum,
    const FUniqueNetIdPtr UserId,
    const FString& Username,
    const FString& EmailAddress,
    const FString& Password,
    const FString& VerificationCode,
    const FOnUpgradeAndVerifyAccountComplete& OnComplete)
    {
    if (!UserId.IsValid())
    {
    UE_LOG_REGISTERUSERINGAME(Warning, TEXT("Failed to upgrade headless account to full account. User id is null."));
    OnComplete.ExecuteIfBound(false, UPGRADE_ACCOUNT_UNKNOWN_ERROR.ToString(), FAccountUserData());
    return;
    }

    FUpgradeAndVerifyRequest Request;
    Request.Username = Username;
    Request.DisplayName = Username;
    Request.UniqueDisplayName = Username;
    Request.EmailAddress = EmailAddress;
    Request.Password = Password;
    Request.Code = VerificationCode;

    AccelByte::FRegistry::User.UpgradeAndVerify2(
    Request,
    THandler<FAccountUserData>::CreateUObject(this, &ThisClass::OnUpgradeAndVerifyAccountSuccess, LocalUserNum, UserId.ToSharedRef(), OnComplete),
    FErrorHandler::CreateWeakLambda(this, [this, OnComplete](int32 ErrorCode, const FString& ErrorMessage)
    {
    UE_LOG_REGISTERUSERINGAME(Warning, TEXT("Failed to upgrade headless account to full account. Error %d: %s"), ErrorCode, *ErrorMessage);
    OnComplete.ExecuteIfBound(false, GetUpgradeAccountErrorMessage((EUpgradeAccountErrorTypes)ErrorCode).ToString(), FAccountUserData());
    })
    );
    }
  5. Now, define the OnUpgradeAndVerifyAccountSuccess() by adding the code below. This code re-queries the user account info to update the user account cache in the AGS OSS.

    void URegisterUserInGameSubsystem_Starter::OnUpgradeAndVerifyAccountSuccess(
    const FAccountUserData& NewFullAccount,
    const int32 LocalUserNum,
    const FUniqueNetIdRef UserId,
    const FOnUpgradeAndVerifyAccountComplete OnComplete)
    {
    UStartupSubsystem* StartupSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<UStartupSubsystem>();
    if (!StartupSubsystem)
    {
    UE_LOG_REGISTERUSERINGAME(Warning, TEXT("Failed to handle on upgrade headless account success. Startup subsystem is null."));
    return;
    }

    // Query new user info to update account user cache on OSS.
    StartupSubsystem->QueryUserInfo(
    LocalUserNum,
    TPartyMemberArray{ UserId },
    FOnQueryUsersInfoCompleteDelegate::CreateWeakLambda(this, [this, LocalUserNum, UserId, NewFullAccount, OnComplete](
    const FOnlineError& Error,
    const TArray<TSharedPtr<FUserOnlineAccountAccelByte>>& UsersInfo)
    {
    if (!Error.bSucceeded || UsersInfo.IsEmpty())
    {
    UE_LOG_REGISTERUSERINGAME(
    Warning,
    TEXT("Failed to upgrade headless account to full account. Error %s: %s"),
    *Error.ErrorCode,
    *Error.ErrorMessage.ToString());
    OnComplete.ExecuteIfBound(false, UPGRADE_ACCOUNT_UNKNOWN_ERROR.ToString(), FAccountUserData());
    return;
    }

    UE_LOG_REGISTERUSERINGAME(Log, TEXT("Success to upgrade headless account to full account: %s"), *NewFullAccount.DisplayName);

    // Update account user cache on SDK.
    AccelByte::FRegistry::Credentials.SetAccountUserData(NewFullAccount);

    OnComplete.ExecuteIfBound(true, TEXT(""), NewFullAccount);
    }));
    }

Resources