Alipay, China's leading third-party online payment solutionAlipay, China's leading third-party online payment solution

2. Implement additional services via wallet API

This document describes the architectural overview that explains how wallet APIs work and the details of each capability provided by wallet APIs.

Interfaces

To be able to use a capability, the wallet first needs to implement the wallet API that represents a capability. To be specific, the following capabilities are available:

  • Mandatory:
    • OAuthService
    • PaymentService
    • CodeService
    • DeepLinkService

When a capability is consumed, the associated JSAPI is called by the merchant and transformed by Griver AppContainer to be the wallet API that interacts with the wallet app, wallet server, and merchant server.

Below is a mapping table that lists the wallet API (interface), its associated JSAPI, and its description.

Interface

Associated JSAPI

Required

Description

OAuthService

getAuthCode()

Mandatory

Retrieve the authorization code from OAuth service that is used for authentication.

PaymentService

tradePay()

Mandatory

Initiate the payment process for purchase scenarios.

CodeService

scan()

Mandatory

Launch code scanner for mini programs to decode QR codes or barcodes.

DeepLinkService

open()

Mandatory

Open an in-app page, navigate from HTML5 web pages to apps, or navigate across apps.

Note:

Customized wallet local logic or JSAPI is out of the scope.

See the details for each capability in the Capabilities.

How it works

Architectural overview

Below is the architectural overview of how Wallet APIs work:

2. Implement additional services via wallet API

This SDK contains three main components:

  • Griver AppContainer

The Mini Program AppContainer. It handles the JS API requests from mini programs and delegates them to the implementation of Wallet API.

  • Core

The core component that handles the workflows of oAuth and deeplinking.

  • Wallet APIs

A set of common interfaces for Griver AppContainer and Core to program, and these interfaces need to be implemented by the Super App. So the SDK can utilize the Super App's existing features to provide services for Core and Griver AppContainer. For instance, the Super App uses the internal QR Code Scanner to implement CodeService. When Griver AppContainer receives the scan() API request from mini program through the Wallet API delegation, the Wallet API calls the implementation of CodeService and eventually opens the Super App's QR Code scanner to complete the task. With Wallet API, the Super App does not need to understand the workflow details for implementing essential JSAPIs.

Capabilities

Capabilities are sets of JSAPIs and wallet APIs that can work together to help users to complete specific tasks. See the details of each capability below:

OAuthService

OAuthService is a service implemented by Wallet to get auth code and process authorization with getAuthCode() JSAPI.

The following shows the interaction sequence, sample implementations, scopes, and more about the OAuthService interface.

Interaction sequence

Currently, there is no association between the client ID that the developer obtained from the Super App OAuth and the mini program appId, and using appId along with the Super App OAuth process will not be successful. Therefore, we need to amend the existing design accordingly.

Proposed solution:

  • In Mini Program Platform, there will be a new field in the Mini Program Management page to allow the developer to configure the Client ID that he or she obtained from the Super App.
  • The configured Client ID (or authClientId) will be sent to Griver AppContainer as part of the Mini Program MetaData.
  • The ACL SDK will receive the authCliendId from Griver AppContainer, and pass it to the ACL SPI Native impl of the Super App, where the Super App can use it to get the auth code from the Super App OAuth Server.

getAuthCode()

2. Implement additional services via wallet API

Explanation

After the mini program invokes the getAuthCode() JSAPI (Step 1), the Griver AppContainer will invoke the getAuthCode() implementation in the Super App (Step 3). Depending on the type of the mini program, the OAuthCodeFlowType parameter will be either LOCAL_MINI_PROGRAM, if the mini program is published to the local Super App workspace only, or ALIPAY_CONNECT, if the mini program is published from the Alipay connect mini program workspace.

In Step 4, the Super App checks with the Super App Server the authInfo of the requested scopes. If the auth is not silent and the scopes are not cached, the Super App should return errorCode=ERROR_CODE_AUTH_PENDING_AGREEMENT (Step 7). Upon receiving ERROR_CODE_AUTH_PENDING_AGREEMENT, the Griver AppContainer will then invoke OAuthService.showAuthPage() (Step 9) to open up the authorization page.

If the user disagrees with the authorization, the Super App should return errorCode=ERROR_CODE_USER_CANCEL (Step 21) and Griver AppContainer will invoke error() callback to the mini program.

If the user agrees with the authorization, the Super App should first cache the scopes  (Step 13), so next time the mini program requests for the same non-silent scope, the user should not be prompted with the authorization page, and then the Super App returns a successful OAuthPageConfirmResult object (an empty OAuthPageConfirmResult object with referenceAgreementId set to null).

The Griver AppContainer will then call OAuthService.getAuthCode again (Step 16). Since the scopes are already cached, the Super App can then proceed to call applyAuthCode() (Step 24), and eventually return authCode and authSuccessScope(scopes) (Step 27), if OAuth request is successful, or return OAuthResult with errorCode=UNKNOWN_ERROR (Step 31) otherwise.

The following is the recommended caching logic that the Super App team can use inside the OAuthService.getAuthCode() implementation. The key should be the combination of appId, authClientId, and the scope, and the value can be a simple boolean true.

copy
private void cacheScopes(String appId, String authClientId, Collection grantedScopes) {
    for (String scope: grantedScopes) {
        String key = appId + "_" + authClientId + "_" + scope;
        cache(key, true); // You can use SharedPreference or any other persistence framework to store it
    }
}

Sample implementation of getAuthCode()

copy
// for getAuthCode JSAPI.
public void getAuthCode(String appId, Set<String> scopes, OAuthCodeFlowType oAuthCodeFlowType,
    Map<String, String> extendedInfo, APIContext apiContext, Callback<OAuthResult> callback) {

    // If user's consent is needed, i.e., the scope is not cached
    if (userConsentNeeded) {
        OAuthResult oAuthResult = new OAuthResult();
        oAuthResult.setResultError(new ResultError(ResultError.ERROR_CODE_AUTH_PENDING_AGREEMENT, ""));
        callback.result(oAuthResult);
        return;
    }
    
    // If Oauth is successful
    if (oAuthSuccessful) {
        OAuthResult oAuthResult = new OAuthResult();
        oAuthResult.authCode = "AUTH_CODE"; // the actual auth code
        oAuthResult.authErrorScopes = new HashMap<>(); // key-value pairs of authError scopes
        oAuthResult.authSuccessScopes = new String[]; // authSucess scopes
        callback.result(oAuthResult);
        return;
    }
    
    // If Oauth is unsuccessful due to other reasons i.e., server error
    if (oAuthUnsuccessful) {
        OAuthResult oAuthResult = new OAuthResult();
        oAuthResult.setResultError(
                    new ResultError(ResultError.ERROR_CODE_UNKNOWN_ERROR, "error_description"));
        callback.result(oAuthResult);
        return;
    }
}

Sample implementation of showAuthPage()

copy
public void showAuthPage(String appId, String name, String logo, Set<String> scopes,
        Map<String, String> extendedInfo, APIContext apiContext, 
                         Callback<OAuthPageConfirmResult> callback) {
    // show user authorization dialog, it is PSP's own implementation
    showAuthorizationPage(appId, name, logo, scopes, new AuthorizationCallback(){
        public void callback(boolean userAgrees) {
            // If User agrees
            if (userAgrees) {
                // Cache scopes
                String authClientId = extendedInfo.get("authClientId");
                cacheScopes(appId, authClientId, scopes);
                callback.result(new OAuthPageConfirmResult(null));
            } else {
                // User disagrees
                OAuthPageConfirmResult oAuthResult = new OAuthPageConfirmResult(null);
                oAuthResult.setResultError(new ResultError(ResultError.ERROR_CODE_USER_CANCEL, ""));
                callback.result(oAuthResult);
            }
        }
    });
}

Sample implementation of getAuthorizedScopes()

This method will be used from 2 places:

  1. In step 6) super app can use this method to get locally cached scopes for checking if the scope has been authorized before.
  2. In mini program settings page, SDK will call this method for displaying current authorized scopes to user.
copy
public void getAuthorizedScopes(String appId, Map<String, String> extendedInfo,APIContext apiContext,
                                Callback<OAuthScopeQueryResult> callback) {
    // A PSP implementation to retrieve a list of scopes that are already authorized / cached
    String [] scopes = getScopesForId(appId);
    OAuthScopeQueryResult result = new OAuthScopeQueryResult();
    result.setAuthorizedScopes(scopes);
    callback.result(result);
}

For Super App Android and iOS engineers

Just focus on the implementation of AuthService as highlighted in blue.

Note that only the implementation of getAuthCode() , showAuthPage() and getAuthorizedScopes() are mandatory, whereas the implementation of other methods in OAuthService are optional for now.

Scopes

The super app needs to support the following scopes:

Open to 3rd-party developers

Scope

Description

auth_base

mandatory

user id / silent auth.

auth_user

mandatory

Group of user attributes

USER_NICKNAME

recommended

Authorized to obtain the user nickname.

SEND_MESSAGE

recommended

Authorized to send message to user.

PLAINTEXT_MOBILE_PHONE

recommended

Authorized to obtain the user mobile number.

USER_NAME

optional

Authorized to obtain the user name.

USER_LOGIN_ID

optional

Authorized to obtain the user login ID.

HASH_LOGIN_ID

optional

Authorized to obtain the hash user login ID.

USER_AVATAR

optional

Authorized to obtain the user avatar.

USER_GENDER

optional

Authorized to obtain the user's gender.

USER_BIRTHDAY

optional

Authorized to obtain the user's birthday.

USER_NATIONALITY

optional

Authorized to obtain the user's nationality.

USER_CONTACTINFO

optional

Authorized to obtain the user's contact.

USER_ADDRESS

optional

Authorized to obtain the user's address.

For internal use only

Scope

Description

auth_user

Invoked by getOpenUserInfo() 

Error codes

Code

Value 

Generic

ERROR_CODE_UNKNOWN_ERROR

1000

ERROR_CODE_USER_CANCEL

1001

ERROR_CODE_APP_SERVICE_ERROR

1002

ERROR_CODE_TIMEOUT

1003

Auth

ERROR_CODE_AUTH_PENDING_AGREEMENT

2001

PaymentService

PaymentService is a service implemented by Wallet to initiate the payment process with tradePay() JSAPI.

The following shows the interaction sequence and other details of the PaymentService interface.

Interaction sequence

2. Implement additional services via wallet API

Explanation

  • In Step 3, depending on the type of order, the payment info returned in Step 4 can be paymentUrl, order ID, or order string.
  • In Step 9, the Super App presents the User with a cashier page, and the User can either confirm or reject this payment.
  • If the User confirms the payment, the Super App forwards the paymentInfo to the Super App Server (Step 11).
  • In Step 12, the Super App Server processes the payment request and informs both the mini program and Merchant Server of the payment result.

For Super App Android and iOS Engineers

Just focus on the implementation of PaymentService as highlighted in blue (Step 9, 11, and 13).

CodeService

CodeService API launches the QR code scanner with the scan() JSAPI. Make sure your app support domain like *.alipaypus.com.

Interaction sequence

2. Implement additional services via wallet API

Explanation

In (Step 1), the mini program invokes the my.scan() JSAPI with either QR or barcode type. This request is first to be served by Griver AppContainer (Step 2). The scan type is translated into ScannerOption by Griver AppContainer (Step 3) and the request is then passed along and eventually handled by the Super App (Step 5).

The Super App can invoke its existing QR code scanning functionality in the implementation of CodeService (Step 5). Based on the ScannerOption, the Super App can use the appropriate image decoding logic to parse the captured image into either QR code or barcode (Step 9) and return the code via Wallet API (Step 13). The code will be eventually made available to mini program in the success callback (Step 15).

If the user opts to cancel the scanning operation, the Super App should return a ScannerResult with the error code ERROR_CODE_USER_CANCEL(Step 6).

If no QR code or barcode is found in the captured image, the Super App should return a ScannerResult with the error code ERROR_CODE_UNKNOWN_ERROR(Step 10).

DeeplinkService

DeepLinkService is a service implemented by the Super App to open an in-app page of the destination scene or navigate to the destination scene across apps.

The DeepLinkService interface provides two methods for the Super App to implement, which are both called open() with different sets of parameters.

Method 1 - Open with URI

Input parameters

Parameter

Date type

Description

scheme

Uri

Immutable URI reference. A URI reference includes a URI and a fragment. The component of the URI is followed by a number sign (#). Build and parse URI references that conform to RFC 2396.

apiContext

APIContext

A context object carries the mini program runtime metadata.

Returns

Date type

Description

Required

boolean

An indicator of whether the scheme is opened successfully.

M

Sample implementation of open with URI

copy
public class DeeplinkServiceProvider implements DeeplinkService {
    @Override
    public boolean open(Uri deepLink) {
        
        //Your operation to open this URI
        if(isAuthorised(deepLink)) {
            //Handles the deeplink either natively, or as webview
        } else {
            //Returns false if deeplink is not handled
            return false;
        }
        
        return true;
    }
}

Method 2 - Open with sceneCode

Input parameters

Parameter

Data type

Description

sceneCode

String

The scene code that presents a scene, such as SCAN and QR_CODE.

params

Map<String, String>

The parameters that the my.navigateToBizScene JSAPI provides, used for data transmission of this JSAPI. 

context

APIContext

A context object carries the mini program runtime metadata.

callback

Callback

The callback allows the developer to send the result back to AC SDK.

Sample implementation of open with sceneCode

copy
public class DeeplinkServiceProvider implements DeeplinkService {

    @Override
    public void open(@NonNull String sceneCode,
                     @NonNull Map<String, String> params,
                     @Nullable APIContext apiContext,
                     @NonNull Callback<BaseResult> openBizSceneCallback) {

        BaseResult baseResult = new BaseResult();
        baseResult.setExtendedInfo(params);
        try {
            // mock handle the navigate scene.
            if (sceneCode.contains("FAILURE")) {
                // mock fail callback
                baseResult.setResultError(new ResultError(
                        ResultError.ERROR_CODE_UNKNOWN_ERROR, "the is mock errorDesc in baseResult."));
            }
        }catch (Exception e) {
            Toast.makeText(MyApplication.get(), "openBizScene exception: " + e, Toast.LENGTH_LONG).show();
        }

        openBizSceneCallback.result(baseResult);
    }
}

Next steps

Start to use Griver AppContainer