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 | Mandatory | Retrieve the authorization code from OAuth service that is used for authentication. | ||
PaymentService | Mandatory | Initiate the payment process for purchase scenarios. | ||
CodeService | Mandatory | Launch code scanner for mini programs to decode QR codes or barcodes. | ||
DeepLinkService | 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](https://ac.alipay.com/storage/2020/5/11/793a3d8d-5270-405b-9362-e6a670b9c842.png)
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](https://ac.alipay.com/storage/2020/5/11/793a3d8d-5270-405b-9362-e6a670b9c842.png)
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.
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()
// 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()
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:
- In step 6) super app can use this method to get locally cached scopes for checking if the scope has been authorized before.
- In mini program settings page, SDK will call this method for displaying current authorized scopes to user.
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 |
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](https://ac.alipay.com/storage/2020/5/11/793a3d8d-5270-405b-9362-e6a670b9c842.png)
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](https://ac.alipay.com/storage/2020/5/11/793a3d8d-5270-405b-9362-e6a670b9c842.png)
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 | 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
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 |
params | Map<String, String> | The parameters that the |
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
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);
}
}