NAV Navbar
Android iOS

Embedded SDK

Prerequisites

Getting Started

In build.gradle, add the Token Maven repo.

repositories {
  maven {
    url https://token.jfrog.io/token/libs-release
  }
}

Add the dependency.

implementation(io.token.sdk:tokenio-sdk-android:2.0.4-beta-12) {
 exclude group: com.google.api.grpc, module: proto-google-common-protos
}

Add the Android settings.

android {
  defaultConfig {
    minSdkVersion 21
    compileSdkVersion 28
    multiDexEnabled = true
  }
  packagingOptions {
    pickFirst "**"
  }
  compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
  }
}

TokenClient

TokenClient is the Token SDK’s unauthenticated call interface. It is used to:

User Authentication Store

UserAuthenticationStore is used to persist the app authentication status. Whenever the user has authenticated, the status must be saved in this shared store so the Token SDK knows the user has been authenticated. After the status is set there is a short window of time for the app to make authenticated calls.

If the status is not authenticated or has expired, the authenticated calls will throw a TokenAuthenticationException.

// Set authentication duration to 10 seconds.
UserAuthenticationStore persistedAuthenticationStore
= new UserAuthenticationStore(10);

// The application context
Context applicationContext;

CryptoEngineFactory engine = new AKSCryptoEngineFactory(
   applicationContext,
   persistedAuthenticationStore);

If you are using Android Fingerprint for authentication, save the status in the onAuthenticationSucceeded method.

class FingerprintUiHelper extends FingerprintManagerCompat.AuthenticationCallback {
  ...

  @Override
  public void onAuthenticationSucceeded(
    FingerprintManagerCompat.AuthenticationResult result) {
    persistedAuthenticationStore.authenticateUser();
  }
}

Creating the TokenClient

Now that we have an instance of the CryptoEngineFactory, we can create a TokenClient object with the builder. This will be the main interface from which to make unauthenticated calls.

TokenClient tokenClient = TokenClient
   .builder()
   .withCryptoEngine(engine)
   .connectTo(TokenCluster.SANDBOX)
   .build();
TokenClientBuilder *builder = [[TokenClientBuilder alloc] init];
// Change the cluster if necessary
builder.tokenCluster = [TokenCluster sandbox];
builder.port = 443;
builder.useSsl = YES;
builder.developerKey = @"Please request one from Token directly";
TokenClient *tokenClient = [builder build];

Note: The developer key in the above sample is for testing only. If you are using the SDK for your application, please request one from Token directly.

Member

Member is the interface to make authenticated API calls on behalf of a user. Use it to:

TKMember is the interface to make authenticated API calls on behalf of the user. Use it to:

The Token SDK will generate keys and store them in Android Keystore. All the authenticated calls are signed with the keys, so Token can confirm the user’s identity.

The Token SDK will generate keys and store them in Secure Enclave. All authenticated calls are signed with the keys, so Token can confirm the user’s identity.

There are three levels of keys in Token:

Whenever the call requires a signature from PRIVILEGED or STANDARD-level keys, the UserAuthenticationStore will check that the user has achieved the sufficient level of authentication required.

Alias

In creating a user member, you must make an alias and identify the realm in which that alias does not already exist. When creating an alias, Token requires that you set the setRealm method with your Bank ID. This allows for aliases to be used by unrelated banks with their own user members.

You can use either the custom type or the defined types (e.g. email, phone) for aliases. Using both types will confuse the Token web-app during resolving aliases. If you want to use the custom type, please provide a short description to Token. This description will show in the web-app when users are entering aliases.

The Token web-app will resolve aliases with “token” realm by default. Please contact Token for configuring it to your realm after your app is ready.

EXAMPLE:

Alias alias = Alias.newBuilder()
   .setValue("example@token.io")
   .setType(Type.EMAIL)
   // The Realm is required.
   .setRealm(bankId)
   .build();
Alias *alias = [[Alias alloc] init];
alias.value = @"example@token.io"
alias.type = Alias_Type_Email;
// The Realm is required.
alias.realm = @"token";

Aliases and member IDs can be looked up using resolveAlias or getMemberId. If you don’t know the type of alias you are searching for, you can set it to UNKNOWN, and it will be resolved by Token.

Aliases and member IDs can be searched using getTokenMember. If you don’t know the type of alias you are searching for, you can set it to Alias_Type_Unknown, and it will be resolved by Token.

tokenClient.resolveAlias(alias)
  .subscribe(tokenMember -> {
    Alias resolved = tokenMember.getAlias();
    String memberId = tokenMember.getId();
  });
[tokenClient getTokenMember:alias
                  onSuccess:^(TokenMember *tokenMember) {
                      if (tokenMember != nil) {
                          // Exists.
                      } else {
                          // Doesn’t exist.
                      }
                  } onError:^(NSError *e) {
                      // Something went wrong.
                  }];

Create a New Member

Create a member for a new user.

For banks using the Token app (registering members under the “token” realm), create a member for a new user as mentioned below.

// User authentication is required.
tokenClient.createMember(alias).subscribe(member -> {});
// User authentication is required.
[tokenClient createMember:alias
                onSuccess:^(TKMember *m) {
                    // Use member.
                    newMember = m;
                } onError:^(NSError *e) {
                    // Something went wrong.
                }];

In the bankId realm, you need to use your own recovery agent. While creating a new user member, you can use the below call to specify the recovery agent who is entitled to sign the recovery authorization on behalf of the user. Alias is the user member’s alias, and recoveryAgent is, for example, the member ID of a Bank member.

// User authentication is required.
tokenClient.createMember(alias, recoveryAgent).subscribe(member -> {});
// User authentication is required.
[tokenClient createMember:alias
            recoveryAgent: bankMemberId
                onSuccess:^(TKMember *m) {
                    // Use member.
                    newMember = m;
                } onError:^(NSError *e) {
                    // Something went wrong.
                }];

In case the recovery agent member ID is not available, it can be fetched by resolving the agent’s alias.

Alias agentAlias = Alias.newBuilder()
   .setValue(BankID)
   .setType(Type.BANK)
   .build();

tokenClient.resolveAlias(agentAlias)
  .subscribe(tokenMember -> {
     String recoveryAgent = tokenMember.getId();
  });
Alias *agentAlias = [[Alias alloc] init];
agentAlias.value = @"bankId";
agentAlias.type = Alias_Type_Bank;

[tokenClient getMemberId:agentAlias
                onSuccess:^(NSString *agentId) {
                    recoveryAgent = agentId;
                } onError:^(NSError *e) {
                    // Something went wrong.
                }];

Check whether the alias has been verified.

member
  .aliases()
  .subscribe(aliases -> {
    if (aliases.contains(alias)) {
      // Verified.
    }});
[newMember getAliases:alias
            onSuccess::^(NSArray<Alias *> *aliasArray) {
                  if ([aliasArray containsObject:alias]) {
                      // Verified.
                  } else {
                      // Not yet Verified.
                  }
            } onError:^(NSError *e) {
                // Something went wrong.
            }];

After the alias is verified, the member is ready to use. Make sure to persist the member ID for future use.

Get an Existing Member

Get the Member object on a device with valid keys.

tokenClient
  .getMember(memberId)
  .subscribe(member -> {});
[tokenClient getMember:memberId
             onSuccess:^(TKMember *m) {
                 // Use member.
                 loginMember = m;
             } onError:^(NSError *e) {
                 // Something went wrong.
             }];

Recover a Member

If the valid device was lost or the app was deleted and there is no existing device that holds a user’s valid keys, the member will need to be recovered. All old keys will be invalidated after recovery and all the bank accounts linked to the recovered member will be marked as locked. The user member will need to relink their accounts to unlock them.

See Create a New Member to create a member with a recovery agent under the bankId realm.

Before recovering the user, a new PRIVILEGED key will need to be generated, and an authorization using that key created. Use CryptoEngine to create a new key. After that, you can generate the authorization for the entitled agent to sign.

Before recovering the user, a new PRIVILEGED key will need to be generated, and an authorization using that key created. Use TKCrypto to create a new key. After that, you can generate the authorization for the entitled agent to sign.

CryptoEngine cryptoEngine
               = new AKSCryptoEngineFactory(
                    Context,
                    new UserAuthenticationStore(),
                    Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
                    .create(userMemberId);
 ;

Key newKey = cryptoEngine.generateKey(PRIVILEGED);
Authorization authorization = tokenClient
       .createRecoveryAuthorizationBlocking(userMemberId, newKey);
// The member id to recover
NSString *userMemberId;

TKCrypto *crypto = [tokenClient createCrypto:userMemberId];
Key *privilegedKey = [crypto generateKey:Key_Level_Privileged];

[tokenClient
createRecoveryAuthorization:userMemberId
key:privilegedKey
onSuccess:^(MemberRecoveryOperation_Authorization *authorization) {
    // Sends the authorization to recovery agent.
} onError:^(NSError *e) {
    // Something went wrong.
}];

The entitled Agent must sign the authorization and use it to generate a MemberRecoveryOperation. For example, if you are using the bank member as the recovery agent, the bank needs to sign the authorization using the Bank SDK.

// This is a java example code that the bank member signs the authorization
Signature agentSignature = bankMember
       .authorizeRecoveryBlocking(authorization);

MemberRecoveryOperation mro =
     MemberRecoveryOperation.newBuilder()
       .setAuthorization(authorization)
       .setAgentSignature(agentSignature)
       .build();

Finally, the user member can be recovered by calling completeRecoveryBlocking with tokenClient.

Finally, the user member can be recovered by calling completeRecovery with tokenClient.

Member recoveredMember = tokenClient.completeRecoveryBlocking(
        userMemberId,
        Arrays.asList(mro),
        newKey,
        cryptoEngine);

// The MemberRecoveryOperation signed by the recovery agent.
MemberRecoveryOperation *mro;
// The member id to recovery
NSString *userMemberId;

// The privileged key for the MemberRecoveryOperation.
Key *privilegedKey;

// The crypto you used to generate the privileged key.
TKCrypto *crypto = [tokenClient createCrypto:userMemberId];
    [tokenClient completeRecovery:userMemberId
               recoveryOperations:[NSArray arrayWithObject:mro]
                    privilegedKey:privilegedKey
                           crypto:crypto
                        onSuccess:^(TKMember *recoveredMember) {
                            // Use member.
                            newMember = recoveredMember;
                        } onError:^(NSError *e) {
                            // Something went wrong.
                        }];

Remember, all aliases of the user will be invalidated after recovery. The bank member must call verifyAliasBlocking again.

bankMember.verifyAliasBlocking(recoveredMember.memberId(), alias);
Member recoveredMember = tokenClient.completeRecoveryBlocking(
        userMemberId,
        Arrays.asList(mro),
        newKey,
        cryptoEngine);

bankMember.verifyAliasBlocking(recoveredMember.memberId(), alias);

Provision a New Device

If you want to load an existing member onto a new device, you will need approval from one of the old devices with the valid keys.

Generate new keys and send them to the old device via notification.

DeviceMetadata metadata = DeviceMetadata.newBuilder()
  .setApplication("Token")
  .setDevice("Android")
  .build();

tokenClient
   .provisionDevice(alias)
   .flatMap(deviceInfo -> tokenClient.notifyAddKey(
       alias,
       deviceInfo.getKeys(),
       metadata))
    .subscribe(notifyStatus -> {});
DeviceMetadata *metadata = [DeviceMetadata message];
metadata.application = @“Token;
metadata.device = @“iPhone;

[tokenClient provisionDevice:alias
                  onSuccess:^(DeviceInfo *deviceInfo) {
                      // Generate the keys.
                      persistedMemberId = deviceInfo.memberId;
                      keys = deviceInfo.keys;
                      [tokenClient notifyAddKey:alias
                                           Keys:keys
                                 deviceMetadata:metadata
                                      onSuccess:^() {
                                          // Notification sent.
                                      } onError:^(NSError *e) {
                                          // Something went wrong.
                                      }];
                  } onError:^(NSError *e) {
                      // Something went wrong.
                  }];

Approve the new keys on the old device with the valid keys. The code below is run on the approved device.

// The notification object you received
Notification notification;

try {
 BodyCase bodyCase =
   BodyCase.valueOf(notification.getContent().getType());
 String payload = notification.getContent().getPayload();
 if (bodyCase.equals(ADD_KEY) ) {
   AddKey.Builder builder = AddKey.newBuilder();
   JsonFormat.parser().merge(payload, builder);
   AddKey addkey = builder.build();
   // Approve the keys
   // User authentication required
   member
     .approveKeys(addkey.getKeysList())
     .subscribe();
 }
} catch (Exception e) {
 // Something went wrong.
}
// The notification object you received
Notification *notification;

if ([notification.content.type isEqualToString:@"ADD_KEY"]) {
    AddKey *content = [TKJson
                       deserializeMessageOfClass:[AddKey class]
                       fromJSON:notification.content.payload];
    // User authentication is required.
    [member approveKeys:content.keysArray
              onSuccess:^() {
                  // Approved.
              } onError:^(NSError *e) {
                  // Something went wrong.
              }];
}

Get the member on the new device.

tokenClient
  .getMember(memberId)
  .subscribe(member -> {});
[tokenClient getMember:persistedMemberId
             onSuccess:^(TKMember *m) {
                 // Use member.
                 loginMember = m;
             } onError:^(NSError *e) {
                 // Something went wrong.
             }];

For receiving and handling notifications, refer to the Notification section of this document.

Profile

Users cannot determine who the member is from a member ID alone. There is the option to set up a profile for the member to make identification easier.

member
  .setProfile(Profile.newBuilder()
    .setDisplayNameFirst("Jon")
    .setDisplayNameLast("Snow")
    .build())
  .subscribe(profile -> {})
// Your profile picture
byte[] picture;

member
  .setProfilePicture("png", picture)
  .subscribe()
Profile *profile = [[Profile alloc] init];
profile.displayNameFirst = @"Jon";
profile.displayNameLast = @"Snow";

[member setProfile:profile
         onSuccess:^(Profile *p) {
             // Success.
         } onError:^(NSError *e) {
             // Something went wrong.
         }];

[member setProfilePicture:member.id
                 withType: @"image/gif"
                 withName: @"selfie.gif"
                 withData: loadImage(@"selfie.gif")
                onSuccess:^() {
                    // Success.
                 } onError:^(NSError *e) {
                     // Something went wrong.
                 }];

You can also look up other members’ profiles with their member ID.

// Other member’s id.
String *otherMemberId;

member
  .getProfile(otherMemberId)
  .subscribe(profile -> {});

member
  .getProfilePicture(otherMemberId, ProfilePictureSize.SMALL)
  .subscribe(blob -> {
     // The picture data.
     byte[] picture = blob.getPayload().getData().toByteArray();
  });
// Other members’ id.
NSString *otherMemberId;

[member getProfile:otherMemberId
         onSuccess:^(Profile *p) {
             // Success.
         } onError:^(NSError *e) {
             // Something went wrong.
         }];

[member getProfilePicture:otherMemberId
                     size:ProfilePictureSize_Small
                onSuccess:^(Blob *blob) {
                    displayImage(blob.data);
                } onError:^(NSError *e) {
                    // Something went wrong.
                }];

Bank Account Linking

Members can easily link and manage their bank accounts. This process can be repeated at a later time to add more accounts from the same bank.

Account linking

To link accounts to a Member, you will need the BankAuthorization. Banks integrated with Token can construct the BankAuthorization in their backend.

Accounts can then be linked.

// The bank authorization of the accounts.
BankAuthorization bankAuthorization;

member .linkAccounts(bankAuthorization) .subscribe(accounts -> {});

// The bank authorization of the accounts.
BankAuthorization *bankAuthorization;

[member linkAccounts:bankAuthorization onSuccess:^(NSArray<TKAccount > accountList) { // Success. } onError:^(NSError *e) { // Something went wrong. }]; }

Get Bank Accounts

Get all bank accounts for a member.

member
  .getAccounts()
  .subscribe(accounts -> {}); // List<Account>
[member getAccounts:^(NSArray<TKAccount *> *accounts) {
     // Success.
} onError:^(NSError *e) {
     // Something went wrong.
}];

Use the Account object to get balances…

Use the TKAccount object to get balances…

// The account you want to look up.
Account account;

account
  .getBalance(Level.LOW)   // Key Level
  .subscribe(balance -> {
    // Balance object includes current and available balance.
    Money money = balance.getCurrent();
  });
// The account you want to look up.
TKAccount * account;

[account getBalance:^(TKBalance *balance) {
     // TKBalance includes current and available balance.
     Money *money = balance.current;
} onError:^(NSError *e) {
     // Something went wrong.
}];

…and transactions.

// The account you want to look up.
Account account;

// NULL: get first "page" of results.
account
  .getTransactions(
     null,       // Offset
     10,         // Per page
     Level.LOW)  // Key Level
  .subscribe(transactionPagedList -> {
     List<Transaction> transactions =
              transactionStringPagedList.getList();
        });
// NULL: get first "page" of results.
[member getTransactionsOffset:NULL
                        limit:10
                   forAccount:account.id
                      withKey:Key_Level_Low
                    onSuccess:^(PagedArray<Transaction *> *transactions) {
                        for (Transaction *t in transactions.items) {
                            // Use transactions.
                        }
                    } onError:^(NSError *e) {
                        // Something went wrong.
}];

Notifications

App push notification services (FCM, APNS etc.) require a secret to send notifications; therefore Token is not able to directly notify user members.

What Token does do, is notify the bank’s backend via the Bank Integration SDK Notification service. The bank’s backend can then forward the notification to the bank’s apps. To enable this feature, the users’ app needs to subscribe to the notification with the member’s bank ID.

Subscriber subscriber = member.subscribeToNotificationsBlocking("bankId");
NSMutableDictionary<NSString *,NSString *> *handlerInstructions = [NSMutableDictionary dictionary];
   [member subscribeToNotifications:@“bankId handlerInstructions:handlerInstructions onSuccess:^(Subscriber *subscriber) {
       // Success.
   } onError:^(NSError * error) {
       // Something went wrong.
   }];

The user will receive a notification if action is required for any of the following:

To use push notifications:

// FCM notification.
public class NotificationService extends FirebaseMessagingService {

  @Override public void onMessageReceived(RemoteMessage remoteMessage) {
    super.onMessageReceived(remoteMessage);
    String notificationId = remoteMessage.getData().get("notification-id");
    // Continue
  }
}

member
  .getNotification(notificationId)
  .subscribe(notification -> {});  // Notification object.
// APNS notification userInfo.
NSDictionary * userInfo;

NSString * notificationId = [userInfo valueForKey:@"notification-id"];
[member getNotification:notificationId
              onSuccess:^(Notification *notification) {
                  // Success.
                  receivedNotification = notification;
              } onError:^(NSError *e) {
                  // Something went wrong.
              }];

To use notification polling:

member
  .getNotifications(
    null,  // Offset.
    10)    // Limit.
  .subscribe(notificationPagedList -> {
    List<Notification> notifications = notificationPagedList.getList();
    if (notifications.size() > 0) {
      Notification notification = notifications.get(0);
      // Continue.
    }
  });
[member
 getNotificationsOffset:NULL
 limit:10
 onSuccess:^(PagedArray<Notification *> *notifications) {
     // Success.
     if (notifications.items.count > 0) {
         receivedNotification = [notifications.items objectAtIndex:0];
     }
 } onError:^(NSError *e) {
     // Something went wrong.
 }];

The polling notifications list is sorted in descending order and by the time created.

IMPORTANT: In order for the push notifications to be sent to your device, your server must be set up with a corresponding backend notification service that can forward Token notifications to your app.

Handling Notifications

Notifications have different types that specify the payload and its content.

You can look up the type in notification.content.type. The payload of the notification is stored at notification.content.payload and serialized in JSON. You can deserialize it with JsonFormat. TKJson.

// The notification object you received.
Notification notification;

try {
  BodyCase bodyCase =
    BodyCase.valueOf(notification.getContent().getType());
  String payload = notification.getContent().getPayload();
  if (bodyCase.equals(ADD_KEY) ) {
    // AddKey is the notification content object for "ADD_KEY" notification.
    AddKey.Builder builder = AddKey.newBuilder();
    JsonFormat.parser().merge(payload, builder);
    AddKey addkey = builder.build();
    // Continue handling AddKey object
  }
} catch (Exception e) {
 // Something went wrong.
}
// AddKey is the notification content object for "ADD_KEY" notification.
 if ([notification.content.type isEqualToString:@"ADD_KEY"]) {
     AddKey *content = [TKJson
                        deserializeMessageOfClass:[AddKey class]
                        fromJSON:notification.content.payload];
     // Continue handling the AddKey object.
}

Merchant Checkout

When a user makes a purchase they will need to approve payment to the merchant. Token will send a CreateAndEndorseToken notification containing a TokenRequest payload. The app will use the information in the TokenRequest, indicated by the BodyCase, to create a transfer token.

// The received notification.
Notification *notification;

try {
  BodyCase bodyCase =
    BodyCase.valueOf(notification.getContent().getType());
  String payload = notification.getContent().getPayload();
  if (bodyCase.equals(CREATE_AND_ENDORSE_TOKEN) ) {
    CreateAndEndorseToken.Builder builder = CreateAndEndorseToken
        .newBuilder();
    JsonFormat.parser().merge(payload, builder);
    CreateAndEndorseToken content = builder.build();
    If (content.getTokenRequest()
               .getRequestPayload()
               .getRequestBodyCase == RequestBodyCase.TRANSFER_BODY) {
      // Merchant checkout notification.
      // Continue.
    }
  }
} catch (Exception e) {
  // Something went wrong.
}
// Received notification.
Notification *notification;

if ([notification.content.type
     isEqualToString:@"CREATE_AND_ENDORSE_TOKEN"]) {
    CreateAndEndorseToken *content = notification
    [TKJson deserializeMessageOfClass:[CreateAndEndorseToken class]
                             fromJSON:notification.content.payload];
    if (content.tokenRequest.requestPayload.requestBodyOneOfCase ==
        TokenRequestPayload_RequestBody_OneOfCase_TransferBody) {
        // Merchant checkout notification.
        // Continue.
    }
}

The transfer token can now be created and endorsed. After the token is endorsed, the user should be prompted to sign the token request state so the merchant can redeem the token.

Create the transfer token.

// The CreateAndEndorseToken content you received.
CreateAndEndorseToken content;
// The ID of account you want to use.
String accountId;

// Create the transfer token from the token request
member
    .createTransferToken(content.getTokenRequest())
    .setAccountId(accountId)
    .execute()
    .subscribe(token -> {});
// The CreateAndEndorseToken content you received.
CreateAndEndorseToken *content;
// The ID of the account you want to use.
NSString *accountId;

TransferTokenBuilder *builder =
[member createTransferToken:content.tokenRequest];
builder.accountId = accountId;

[builder executeAsync:^(Token *token) {
    //continue to endorse token
} onError:^(NSError *e) {
    // Something went wrong.
}];

Endorse the transfer token.

// The transfer token you created.
Token token;

member
 // User authentication required.
  .endorseToken(token, STANDARD)
  .subscribe(tokenOperationResult -> {
      //Continue to sign token request state.
  });
// User authentication required.
[member endorseToken:token
             withKey:Key_Level_Standard
           onSuccess:^(TokenOperationResult *result) {
               //Continue to sign token request state.
           } onError:^(NSError *e) {
               // Something went wrong.
           }];

Sign the token request state.

// The token operation result from endorse token
TokenOperationResult tokenOperationResult;

member.signTokenRequestState(
       content.getTokenRequest().getId(),
       tokenOperationResult.getToken().getId(),
       content.getTokenRequest().getRequestPayload().getCallbackState()))
   .subscribe(ignore -> {});
[member signTokenRequestState:content.tokenRequestId
                      tokenId:result.token.id_p
                        state:content.state
                    onSuccess:^(Signature *ignore) {
                        // Success.
                    } onError:^(NSError *e) {
                        // Something went wrong.
                    }];

Accessing Accounts

Access Tokens

NOTE: In this document and elsewhere, access token refers to a Token-specific concept. It is a smart token that accesses end-user bank account information.

When a user wants to share their account information with a third party, they need to approve an access token request. Token will send a CreateAndEndorseToken notification to the app, and use the information in the TokenRequest, indicated by the BodyCase, to create an access token.

// The received notification
Notification *notification;

try {
  BodyCase bodyCase =
    BodyCase.valueOf(notification.getContent().getType());
  String payload = notification.getContent().getPayload();
  if (bodyCase.equals(CREATE_AND_ENDORSE_TOKEN) ) {
    CreateAndEndorseToken.Builder builder = CreateAndEndorseToken
        .newBuilder();
    JsonFormat.parser().merge(payload, builder);
    CreateAndEndorseToken content = builder.build();
        If (content.getTokenRequest()
               .getRequestPayload()
               .getRequestBodyCase == RequestBodyCase.ACCESS_BODY) {
      // It is an access token request notification
      // Continue
    }
  }
} catch (Exception e) {
  // Something went wrong.
}
// The received notification.
Notification *notification;

if ([notification.content.type
     isEqualToString:@"CREATE_AND_ENDORSE_TOKEN"]) {
    CreateAndEndorseToken *content =
    [TKJson deserializeMessageOfClass:[CreateAndEndorseToken class]
                             fromJSON:notification.content.payload];
    if (content.tokenRequest.requestPayload.requestBodyOneOfCase ==
        TokenRequestPayload_RequestBody_OneOfCase_AccessBody) {
        // It is an access token request notification.
        // Continue.
    }
}

The AccessTokenBuilder is used to create access tokens. After creating the AccessTokenBuilder object, you can add resources for each account the user wants to share.

// The CreateAndEndorseToken content you received.
CreateAndEndorseToken content;
// The ID of the account you want to share.
String accountId;


AccessTokenBuilder builder = AccessTokenBuilder.fromTokenRequest(
    content.getTokenRequest());

builder.forAccount(accountId);
builder.forAccountTransactions(accountId);
builder.forAccountBalances(accountId);

member
    .createAccessToken(builder)
    .subscribe(token -> {});
// The CreateAndEndorseToken content you received.
CreateAndEndorseToken *content;
// The ID of the account you want to share.
NSString *accountId;

AccessTokenBuilder *builder = [[AccessTokenBuilder alloc]
                                initWithTokenRequest:content.tokenRequest];
[builder forAccount:accountId];
[builder forAccountBalances:accountId];
[builder forAccountTransactions:accountId];

[member createAccessToken:builder
                onSuccess:^(Token *token) {
                    //Continue to endorse token.
                } onError:^(NSError *e) {
                    // Something went wrong.
                }];

Endorse the access token.

// The transfer token you created.
Token token;

member
 // User authentication required.
  .endorseToken(token, STANDARD)
  .subscribe(tokenOperationResult -> {
      //Continue to sign token request state.
  });
// User authentication required.
[member endorseToken:token
             withKey:Key_Level_Standard
           onSuccess:^(TokenOperationResult *result) {
               //Continue to sign token request state
           } onError:^(NSError *e) {
               // Something went wrong.
           }];

Sign the token request state.

// The token operation result from endorse token
TokenOperationResult tokenOperationResult;

member.signTokenRequestState(
       content.getTokenRequest().getId(),
       tokenOperationResult.getToken().getId(),
       content.getTokenRequest().getRequestPayload().getCallbackState()))
   .subscribe(ignore -> {});
[member signTokenRequestState:content.tokenRequestId
                      tokenId:result.token.id_p
                        state:content.state
                    onSuccess:^(Signature *ignore) {
                        // Success.
                    } onError:^(NSError *e) {
                        // Something went wrong.
                    }];

P2P Payment

Users can pay other Token users and legacy users with transfer tokens. Token users will receive payments into their default account.

Create Transfer Token

Create a transfer token to transfer funds to other Token users.

// the member ID of payee.
String payeeId;
// The ID of account you want to use.
String accountId;
// The amount you want to send.
double amount;

member
    .createTransferToken(amount, "EUR")
    .setToMemberId(payeeId)
    .setAccountId(accountId)
    .setDescription("Book purchase")
    .setEffectiveAtMs(System.currentTimeMillis())
    .execute()
    .subscribe(token -> {});
// the member ID of the payee.

NSString *payeeId;
// The ID of account you want to use.
NSString *accountId;
// The amount you want to send.
NSDecimalNumber *amount;

TransferTokenBuilder *builder =
    [member createTransferToken:amount currency: @"EUR"];
builder.toMemberId = payeeId;
builder.accountId = accountId;
builder.descr = @"Book purchase";

// This token can only be endorsed before this effective time.
builder.effectiveAtMs = [[NSDate date] timeIntervalSince1970] * 1000.0;

[builder executeAsync:^(Token *token) {
    // Continue to endorse token.
} onError:^(NSError *e) {
    // Something went wrong.
}];

To create a transfer token to users without using Token aliases (e.g. using SWIFT scheme), construct the TransferEndpoint object. Instead of setting toMemberId in TransferTokenBuilder, set the destination directly.

// The id of the account you want to use.
String accountId;
// The amount you want to send.
double amount;

// payee's SWIFT account
String iban = "";
// payee's SWIFT BIC
String bic = "";
// payee's legal name
String legalName = "";
// payee's country
String country = "";

member
    .createTransferToken(amount, "EUR")
    .addDestination(TransferEndpoint.newBuilder()
        .setAccount(BankAccount.newBuilder()
            .setSwift(Swift.newBuilder()
                .setAccount(iban)
                .setBic(bic)))
        .setCustomerData(CustomerData.newBuilder()
            .addLegalNames(legalName)
            .setAddress(
                AddressProtos.Address.newBuilder()
                    .setCountry(country)
                    .build())
            .build())
        .build())
    .setAccountId(accountId)
    .setDescription("Book purchase")
    .setEffectiveAtMs(System.currentTimeMillis())
    .execute()
    .subscribe(token -> {});
// Payee's SWIFT account.
NSString *iban;
// Payee's SWIFT BIC.
NSString *bic;
// The id of account you want to use.
NSString *accountId;
// The amount you want to send.
NSDecimalNumber *amount;

TransferTokenBuilder *builder =
    [member createTransferToken:amount currency: @"EUR"];
builder.accountId = accountId;
TransferEndpoint *endpoint = [[TransferEndpoint alloc] init];
endpoint.account.swift.account = iban;
endpoint.account.swift.bic = bic;
builder.destinations = @[endpoint];

// This token can only be endorsed before this effective time.
builder.effectiveAtMs = [[NSDate date] timeIntervalSince1970] * 1000.0;

[builder executeAsync:^(Token *token) {
    // Continue to endorse token.
} onError:^(NSError *e) {
    // Something went wrong.
}];

Payment Request

Users can request payments from other Token users.

The payee notifies the payer of the payment request.

// Payee member object.
Member payee;
// Payer's member Id.
String payerId;

TokenPayload payload = TokenPayload.newBuilder()
        .setDescription("Lunch")
        .setFrom(TokenMember.newBuilder().setId(payerId).build())
        .setTo(TokenMember.newBuilder().setId(payee.memberId()).build())
        .setTransfer(TransferBody.newBuilder()
            .setAmount("100")
            .setCurrency("EUR")
            .build())
        .build();
    tokenClient.notifyPaymentRequest(payload).subscribe(notifyStatus -> {});
// Payee member object.
TKMember *payee;
// Payer's member Id.
NSString *payerId;

TokenPayload *payload = [TokenPayload message];
payload.description_p = @"lunch";
payload.from.id_p = payer.id;
payload.to.id_p = payee.id;
payload.transfer.lifetimeAmount = @"100";
payload.transfer.currency = @"EUR";

[tokenClient notifyPaymentRequest:payload
                        onSuccess:^ {
                            // Notification sent.
                        } onError:^(NSError *e) {
                            // Something went wrong.
                        }];

The payer receives a notification.

// The received notification.
Notification notification;

try {
 BodyCase bodyCase =
     BodyCase.valueOf(notification.getContent().getType());
 String payload = notification.getContent().getPayload();
 if (bodyCase.equals(PAYMENT_REQUEST) ) {
   PaymentRequest.Builder builder = PaymentRequest.newBuilder();
   JsonFormat.parser().merge(payload, builder);
   PaymentRequest content = builder.build();
   // The same payload from payee.
   TokenPayload payload = content.getPayload();
   // Continue.
 }
} catch (Exception e) {
 // Something went wrong.
}
// The received notification.
Notification *notification;

if ([notification.content.type isEqualToString:@"PAYMENT_REQUEST"]) {
    PaymentRequest *content =
    [TKJson deserializeMessageOfClass:[PaymentRequest class]
                             fromJSON:notification.content.payload];
    // The same payload from payee.
    TokenPayload *receivedPayload = content.payload.
    // Continue.
}

Now the payer has enough information to create a transfer token.

// The id of account you want to use.
String accountId;
// The payload you want received.
TokenPayload receivedPayload;

member
  .createTransferToken(
    receivedPayload.getTransfer().getLifetimeAmount(),
    receivedPayload.getTransfer().getCurrency())
  .setToMemberId(receivedPayload.getTo().getId())
  .setAccountId(accountId)
  .setDescription(receivedPayload.getDescription())
  .setEffectiveAtMs(System.currentTimeMillis())
  .execute()
  .subscribe(token -> {});
// The id of account you want to use.
NSString *accountId;
// The payload you want received.
TokenPayload *receivedPayload;


NSDecimalNumber *amount = [NSDecimalNumber decimalNumberWithString:
                           receivedPayload.transfer.lifetimeAmount];

TransferTokenBuilder *builder =
    [member createTransferToken:amount
                       currency:receivedPayload.transfer.currency];
builder.fromMemberId = receivedPayload.from.id_p;
builder.toMemberId = receivedPayload.to.id_p;
builder.accountId = accountId;
builder.descr = receivedPayload.description_p;
// This token can only be endorsed before this effective time.
builder.effectiveAtMs = [[NSDate date] timeIntervalSince1970] * 1000.0;

[builder executeAsync:^(Token *token) {
    // Continue to endorse token.
} onError:^(NSError *e) {
    // Something went wrong.
}];

Endorse and Redeem Transfer Token

After redemption, the transfer will proceed between both users’ accounts.

member.endorseToken(token, STANDARD)
   .flatMap(tokenOperationResult ->
       member.redeemToken(tokenOperationResult.getToken()))
   .subscribe(transfer -> {});
// User authentication required.
[member endorseToken:token
             withKey:Key_Level_Standard
           onSuccess:^(TokenOperationResult *result) {
               // Continue redeem token.
           } onError:^(NSError *e) {
               // Something went wrong.
           }];
[member redeemToken:token
          onSuccess:^(Transfer *transfer) {
              // Success.
          } onError:^(NSError *e) {
              // Something went wrong.
          }];

Copyright © 2019 Token, Inc. All Rights Reserved