Home Reference Source Test

src/main/Member.js

// @flow
import {Member as CoreMember, Account} from '@token-io/core';
import config from '../config.json';
import Representable from './Representable';
import TokenRequestBuilder from './TokenRequestBuilder';
import AuthHttpClient from '../http/AuthHttpClient';
import HttpClient from '../http/HttpClient';
import Util from '../Util';
import type {
    Blob,
    BlobPayload,
    Profile,
    ProfilePictureSize,
    Token,
    TokenRequest,
    TokenOperationResult,
    Transfer,
    StandingOrderSubmission,
    KeyStoreCryptoEngine,
    TransferDestination,
    BulkTransfer,
    VerifyEidasPayload,
    VerifyEidasResponse,
    CustomerTrackingMetadata,
    GetEidasVerificationStatusResponse,
    WebhookConfig,
    MiscHeaders,
    InitiateBankAuthorizationResponse,
} from '@token-io/core';

/**
 * Represents a Token member.
 */
export default class Member extends CoreMember {
    constructor(options: {
        // Token environment to target
        env: string,
        memberId: string,
        // CryptoEngine for the member's keys
        cryptoEngine: Class<KeyStoreCryptoEngine>,
        // dev key
        developerKey?: string,
        // callback to invoke on any cross-cutting RPC
        globalRpcErrorCallback?: ({name: string, message: string}) => void,
        // enable HTTP error logging if true
        loggingEnabled?: boolean,
        // override the default SDK URL
        customSdkUrl?: string,
        // custom HTTP response interceptor for axios
        customResponseInterceptor?: Object,
    }) {
        super(options);
        this._unauthenticatedClient = new HttpClient(options);
        this._client = new AuthHttpClient(options);
    }

    /**
     * Looks up a member's account by ID.
     *
     * @param accountId - accountId
     * @return Promise resolving to the account
     */
    getAccount(accountId: string): Promise<Account> {
        return Util.callAsync(this.getAccount, async () => {
            const res = await this._client.getAccount(accountId);
            return new Account(res.data.account, this);
        });
    }

    /**
     * Looks up the member's accounts.
     *
     * @return Promise resolving to the accounts
     */
    getAccounts(): Promise<Array<Account>> {
        return Util.callAsync(this.getAccounts, async () => {
            const res = await this._client.getAccounts();
            return res.data.accounts && res.data.accounts.map(a => new Account(a, this)) || [];
        });
    }

    /**
     * Creates a representable that acts as another member via an access token.
     *
     * @param accessTokenId - ID of the access token
     * @param {CustomerTrackingMetadata} customerTrackingMetadata
     * @param {boolean} customerInitiated
     * @return new member that acts as another member
     */
    forAccessToken(
        accessTokenId: string,
        customerInitiated?: boolean,
        customerTrackingMetadata: CustomerTrackingMetadata,
    ): Representable {
        return Util.callSync(this.forAccessToken, () => {
            const newMember = new Member(this._options);

            if(customerTrackingMetadata && Object.keys(customerTrackingMetadata).length === 0){
                throw new Error('User tracking metadata is empty.\
                 Use forAccessToken(String) instead.');
            } else if(!customerTrackingMetadata){
                newMember._client.useAccessToken(accessTokenId, customerInitiated);
                return new Representable(newMember);
            }

            newMember._client.useAccessToken(accessTokenId, true, customerTrackingMetadata);
            return new Representable(newMember);
        });
    }

    /**
     * Replaces the authenticated member's public profile.
     *
     * @param profile - profile to set
     * @return newly-set profile
     */
    setProfile(profile: Profile): Promise<Profile> {
        return Util.callAsync(this.setProfile, async () => {
            const res = await this._client.setProfile(profile);
            return res.data.profile;
        });
    }

    /**
     * Replaces the authenticated member's public profile name.
     *
     * @param profileName - profile name to set
     * @return empty promise
     */
    setProfileName(profileName: string): Promise<void> {
        return Util.callAsync(this.setProfileName, async () => {
            await this._client.setProfileName(profileName);
        });
    }

    /**
     * Gets a member's public profile.
     *
     * @param id - member ID whose profile to get
     * @return profile
     */
    getProfile(id: string): Promise<Profile> {
        return Util.callAsync(this.getProfile, async () => {
            const res = await this._client.getProfile(id);
            return res.data.profile;
        });
    }

    /**
     * Gets a member's public profile name.
     *
     * @param id - member ID whose profile to get
     * @return profile name
     */
    getProfileName(id: string): Promise<string> {
        return Util.callAsync(this.getProfile, async () => {
            const res = await this._client.getProfileName(id);
            return res.data.profileName;
        });
    }

    /**
     * Uploads the authenticated member's public profile.
     *
     * @param type - MIME type
     * @param data - data in bytes, can be base64 string
     * @return empty promise
     */
    setProfilePicture(
        type: string,
        data: ArrayBuffer | string
    ): Promise<void> {
        return Util.callAsync(this.setProfilePicture, async () => {
            await this._client.setProfilePicture(type, data);
        });
    }

    /**
     * Gets a member's public profile picture.
     *
     * @param id - member ID whose picture to get
     * @param size - desired size category SMALL/MEDIUM/LARGE/ORIGINAL
     * @return downloaded blob
     */
    getProfilePicture(
        id: string,
        size: ProfilePictureSize
    ): Promise<Blob> {
        return Util.callAsync(this.getProfilePicture, async () => {
            const res = await this._client.getProfilePicture(id, size);
            return res.data.blob;
        });
    }

    /**
     * Creates a customization.
     *
     * @param logo - logo
     * @param colors - map of ARGB colors #AARRGGBB
     * @param consentText - consent text
     * @param name - display name
     * @param appName - corresponding app name
     * @return customization ID
     */
    createCustomization(
        logo: BlobPayload,
        colors: ?{[string]: string},
        consentText: ?string,
        name: ?string,
        appName: ?string
    ): Promise<string> {
        return Util.callAsync(this.createCustomization, async () => {
            const res = await this._client.createCustomization(
                logo,
                colors,
                consentText,
                name,
                appName);
            return res.data.customizationId;
        });
    }

    /**
     * Stores a request for a token. Called by a merchant or a TPP that wants access from a user.
     *
     * @param tokenRequest - token request to store
     * @return the stored TokenRequestBuilder
     */
    storeTokenRequest(tokenRequest: TokenRequestBuilder): Promise<TokenRequest> {
        return Util.callAsync(this.storeTokenRequest, async () => {
            tokenRequest.requestPayload.callbackState =
                encodeURIComponent(JSON.stringify(tokenRequest.requestPayload.callbackState));
            const res = await this._client.storeTokenRequest(tokenRequest);
            return res.data.tokenRequest;
        });
    }

    /**
     * Looks up a token by its ID.
     *
     * @param tokenId - ID of the token
     * @return token
     */
    getToken(tokenId: string): Promise<Token> {
        return Util.callAsync(this.getToken, async () => {
            const res = await this._client.getToken(tokenId);
            return res.data.token;
        });
    }

    /**
     * Looks up all transfer tokens.
     *
     * @param offset - where to start looking
     * @param limit - how many to look for
     * @return returns a list of Transfer Tokens
     */
    getTransferTokens(
        offset: string,
        limit: number
    ): Promise<{tokens: Array<Token>, offset: string}> {
        return Util.callAsync(this.getTransferTokens, async () => {
            const res = await this._client.getTokens('TRANSFER', offset, limit);
            return {
                tokens: res.data.tokens || [],
                offset: res.data.offset,
            };
        });
    }

    /**
     * Looks up all access tokens.
     *
     * @param offset - where to start looking
     * @param limit - how many to look for
     * @return access tokens - returns a list of access tokens
     */
    getAccessTokens(
        offset: string,
        limit: number
    ): Promise<{tokens: Array<Token>, offset: string}> {
        return Util.callAsync(this.getAccessTokens, async () => {
            const res = await this._client.getTokens('ACCESS', offset, limit);
            return {
                tokens: res.data.tokens || [],
                offset: res.data.offset,
            };
        });
    }

    /**
     * Cancels a token.
     *
     * @param token - token to cancel, can be tokenId
     * @return cancelled token
     */
    cancelToken(token: Token | string): Promise<TokenOperationResult> {
        return Util.callAsync(this.cancelToken, async () => {
            const finalToken = await this._resolveToken(token);
            const cancelled = await this._client.cancelToken(finalToken);
            if (typeof token !== 'string') {
                token.payloadSignatures = cancelled.data.result.token.payloadSignatures;
            }
            return cancelled.data.result;
        });
    }

    /**
     * Redeems a token.
     *
     * @param token - token to redeem. Can also be a tokenId
     * @param amount - amount to redeem
     * @param currency - currency to redeem
     * @param description - optional transfer description
     * @param destinations - transfer destinations
     * @param refId - ID that will be set on created Transfer.
     *                Token uses this to detect duplicates.
     *                Caller might use this to recognize the transfer.
     *                If param empty, transfer will have random refId.
     * @return Transfer created as a result of this redeem call
     */
    redeemToken(
        token: Token | string,
        amount?: number,
        currency?: string,
        description?: string,
        destinations?: Array<TransferDestination> = [],
        refId?: string
    ): Promise<Transfer> {
        return Util.callAsync(this.redeemToken, async () => {
            const finalToken = await this._resolveToken(token);
            if (!amount) {
                amount = finalToken.payload.transfer.lifetimeAmount;
            }
            if (!currency) {
                currency = finalToken.payload.transfer.currency;
            }
            if (!refId) {
                if (amount === finalToken.payload.transfer.lifetimeAmount) {
                    refId = finalToken.payload.refId;
                } else {
                    refId = Util.generateNonce();
                }
            }
            if (!description) {
                description = finalToken.payload.description;
            }
            if (Util.countDecimals(amount) > config.decimalPrecision) {
                throw new Error(
                    `Number of decimals in amount should be at most ${config.decimalPrecision}`);
            }
            const res = await this._client.redeemToken(
                finalToken,
                amount,
                currency,
                description,
                destinations,
                refId);
            return res.data.transfer;
        });
    }

    /**
     * Looks up a transfer.
     *
     * @param transferId - ID to look up
     * @return transfer if found
     */
    getTransfer(transferId: string): Promise<Transfer> {
        return Util.callAsync(this.getTransfer, async () => {
            const res = await this._client.getTransfer(transferId);
            return res.data.transfer;
        });
    }

    /**
     * Looks up all of the member's transfers.
     *
     * @param tokenId - token to use for lookup
     * @param offset - where to start looking
     * @param limit - how many to retrieve
     * @return Transfers
     */
    getTransfers(
        tokenId: string,
        offset: string,
        limit: number
    ): Promise<{transfers: Array<Transfer>, offset: string}> {
        return Util.callAsync(this.getTransfers, async () => {
            const res = await this._client.getTransfers(tokenId, offset, limit);
            return {
                transfers: res.data.transfers || [],
                offset: res.data.offset,
            };
        });
    }

    /**
     * Redeems a standing order token.
     *
     * @param {string} tokenId - token to redeem. Can also be a tokenId
     * @return standing order submission created as a result of this redeem call
     */
    redeemStandingOrderToken(
        tokenId: string,
    ): Promise<StandingOrderSubmission> {
        return Util.callAsync(this.redeemStandingOrderToken, async () => {
            const res = await this._client.redeemStandingOrderToken(tokenId);
            if (res.data.submission.status === 'FAILED') {
                const error: Object = new Error('FAILED');
                error.authorizationDetails = res.data.authorizationDetails;
                throw error;
            }
            return res.data.submission;
        });
    }

    /**
     * Redeems a bulk transfer token.
     *
     * @param tokenId ID of token to redeem
     * @return bulk transfer record
     */
    redeemBulkTransferToken(tokenId: string): Promise<BulkTransfer> {
        return Util.callAsync(this.redeemBulkTransferToken, async () => {
            const res =  await this._client.createBulkTransfer(tokenId);
            return res.data.transfer;
        });
    }

    /**
     * Looks up an existing bulk transfer.
     *
     * @param bulkTransferId
     * @return bulk transfer record
     */
    getBulkTransfer(bulkTransferId: string): Promise<BulkTransfer> {
        return Util.callAsync(this.getBulkTransfer, async () => {
            const res = await this._client.getBulkTransfer(bulkTransferId);
            return res.data.bulkTransfer;
        });
    }

    /**
     * Looks up an existing Token standing order submission.
     *
     * @param submissionId - ID of the standing order submission
     * @return standing order submission
     */
    getStandingOrderSubmission(submissionId: string): Promise<StandingOrderSubmission> {
        return Util.callAsync(this.getStandingOrderSubmission, async () => {
            const res = await this._client.getStandingOrderSubmission(submissionId);
            return res.data.submission;
        });
    }

    /**
     * Looks up existing Token standing order submissions.
     *
     * @param offset - optional where to start looking
     * @param limit - how many to retrieve
     * @return standing order submissions
     */
    getStandingOrderSubmissions(
        offset: string,
        limit: string
    ): Promise<{submissions: Array<StandingOrderSubmission>, offset: string}> {
        return Util.callAsync(this.getStandingOrderSubmissions, async () => {
            const res = await this._client.getStandingOrderSubmissions(offset, limit);
            return {
                submissions: res.data.submissions || [],
                offset: res.data.offset,
            };
        });
    }

    /**
     * Sets destination account for once if it hasn't been set.
     *
     * @param tokenRequestId token request Id
     * @param transferDestinations destination account
     * @return observable that completes when request handled
     */
    setTokenRequestTransferDestinations(
        tokenRequestId: string,
        transferDestinations: Array<TransferDestination>
    ): Promise<{}> {
        return Util.callAsync(this.setTokenRequestTransferDestinations, async () => {
            return await this._client.setTokenRequestTransferDestinations(
                tokenRequestId,
                transferDestinations);
        });
    }

    /**
     * Downloads a blob from the server.
     *
     * @param blobId - ID of the blob
     * @return downloaded blob
     */
    getBlob(blobId: string): Promise<Blob> {
        return Util.callAsync(this.getBlob, async () => {
            const res = await this._client.getBlob(blobId);
            return res.data.blob;
        });
    }

    _resolveToken(token: string | Token): Promise<Token> {
        return new Promise(resolve => {
            if (typeof token === 'string') {
                this.getToken(token)
                    .then(lookedUp => resolve(lookedUp));
            } else {
                // token is already in JSON representation
                resolve(token);
            }
        });
    }

    /**
     * Verifies eIDAS alias with an eIDAS certificate, containing auth number equal to the value
     * of the alias. Before making this call make sure that:<ul>
     *     <li>The member is under the realm of a bank (the one tpp tries to gain access to)</li>
     *     <li>An eIDAS-type alias with the value equal to auth number of the TPP is added
     *     to the member</li>
     *     <li>The realmId of the alias is equal to the member's realmId</li>
     *</ul>
     *
     * @param payload - payload containing the member id and the base64 encoded eIDAS certificate
     * @param signature - the payload signed with a private key corresponding to the certificate
     * @return a result of the verification process, including verification status and
     *       verificationId that can be used later to retrieve the status of the verification using
     *       getEidasVerificationStatus call.
     */
    verifyEidas(payload: VerifyEidasPayload, signature: string): Promise<VerifyEidasResponse> {
        return Util.callAsync(this.verifyEidas, async () => {
            const res = await this._client.verifyEidas(payload, signature);
            return res.data;
        });
    }

    /**
     * Retrieves an eIDAS verification status by verificationId.
     *
     * @param verificationId verification id
     * @return a status of the verification operation together with the certificate and alias value
     */
    async getEidasVerificationStatus(
        verificationId: string
    ): Promise<GetEidasVerificationStatusResponse> {
        return Util.callAsync(this.getEidasVerificationStatus, async () => {
            const res = await this._client.getEidasVerificationStatus(verificationId);
            return res.data;
        });
    }

    /**
     * Initiate bank authorization.
     *
     * @param {string} tokenRequestId Token request id
     * @param {Object} credentials map of credentials
     * @param {boolean} consentAccepted if consent accepted by user/payer
     * @param {boolean} useCredentialFlow Use credential flow
     * @param {boolean} useWebappCredentialsFlow Use webapp credentials flow
     * @returns {Object} response to the api call
     */
    async initiateBankAuthorization(
        tokenRequestId: string,
        credentials: ?{[string]: string},
        consentAccepted: boolean,
        useCredentialFlow: boolean,
        useWebappCredentialsFlow: boolean
    ): Promise<InitiateBankAuthorizationResponse> {
        return Util.callAsync(this.initiateBankAuthorization, async () => {
            const res = await this._client.initiateBankAuthorization(
                tokenRequestId,
                credentials,
                consentAccepted,
                useCredentialFlow,
                useWebappCredentialsFlow,
            );
            return res.data;
        });
    }

    /**
     * Get url to bank authorization page for a token request.
     *
     * @param bankId {string} Bank Id
     * @param tokenRequestId {string} Token Request Id
     * @param {boolean} consentAccepted consent accepted by user/payer
     * @returns {string} url
     */
    getBankAuthUrl(bankId: string, tokenRequestId: string, consentAccepted: boolean): Promise<string> {
        return Util.callAsync(this.getBankAuthUrl, async () => {
            const res = await this._client.getBankAuthUrl(bankId, tokenRequestId, consentAccepted);
            return res.data.url;
        });
    }

    /**
     * Forward the callback from the bank (after user authentication) to Token.
     *
     * @param bankId {string} Bank Id
     * @param query {string} HTTP query string
     * @returns {string} token request ID
     */
    onBankAuthCallback(bankId: string, query: string): Promise<string> {
        return Util.callAsync(this.onBankAuthCallback, async () => {
            const encodedQuery = query && encodeURIComponent(query) || '';
            const res = await this._client.onBankAuthCallback(bankId, encodedQuery);
            return res.data.tokenRequestId;
        });
    }

    /**
     * Get the raw consent from the bank associated with a token.
     *
     * @param tokenId {string} Token Id
     * @returns {string} raw consent
     */
    getRawConsent(tokenId: string): Promise<string> {
        return Util.callAsync(this.getRawConsent, async () => {
            const res = await this._client.getRawConsent(tokenId);
            return res.data.consent;
        });
    }

    /**
     * Set a webhook config. The config contains a url and a list of event types.
     *
     * @param {WebhookConfig} config - webhook config
     * @returns empty
     */
    setWebhookConfig(config: WebhookConfig): Promise<void> {
        return Util.callAsync(this.setWebhookConfig, async () => {
            await this._client.setWebhookConfig(config);
        });
    }

    /**
     * Get the webhook config.
     *
     * @returns {WebhookConfig} webhook config
     */
    getWebhookConfig(): Promise<WebhookConfig> {
        return Util.callAsync(this.getWebhookConfig, async () => {
            const res = await this._client.getWebhookConfig();
            return res.data.config;
        });
    }

    /**
     * Delete the webhook config.
     *
     * @returns empty
     */
    deleteWebhookConfig(): Promise<void> {
        return Util.callAsync(this.deleteWebhookConfig, async () => {
            await this._client.deleteWebhookConfig();
        });
    }

    /**
     * Populate customer tracking metadata headers in token calls.
     *
     * @param {CustomerTrackingMetadata} customerTrackingMetadata
     */
    addCustomerTrackingMetadata(
        customerTrackingMetadata: CustomerTrackingMetadata,
    ): void {
        return Util.callSync(this.addCustomerTrackingMetadata, () => {
            if(customerTrackingMetadata && Object.keys(customerTrackingMetadata).length === 0){
                throw new Error('User tracking metadata is empty.');
            }

            this._client.addCustomerTrackingMetadata(customerTrackingMetadata);
        });
    }

    /**
     * Populate misc headers in token calls
     *
     * @param {MiscHeaders} miscHeaders
     */
    setMiscHeaders(
        miscHeaders: MiscHeaders,
    ): void {
        return Util.callSync(this.setMiscHeaders, () => {
            if(miscHeaders && Object.keys(miscHeaders).length === 0){
                throw new Error('Misc headers are empty.');
            }

            this._client.setMiscHeaders(miscHeaders);
        });
    }

}