從零開始詳解應用內支付:商品建立及測試上架

當用戶在應用市場裏下載APP,體驗了免費的基礎功能和服務後,經過自主選擇付費,以獲取和使用應用內提供的增值服務——這種用戶應用內購買行爲的完成,就須要用到應用內支付(In-App Purchases,簡稱IAP)。html

應用內支付有豐富的使用場景和需求,遊戲類應用中裝備和虛擬幣的購買、視頻類應用中會員的訂閱、知識工具類應用中一次性購買開高級服務和功能……應用內支付大大拓寬了移動開發的盈利模式,增長了APP營收的靈活性。java

在華爲移動生態體系中,咱們爲開發者提供了安全好用的應用內支付基礎開發服務,助力開發者降本增效,實現付費轉化和持續營收。本文將詳解應用內支付開通啓用、商品管理、測試和上架等環節操做步驟,呈現應用內商品生成全過程,讓開發者輕鬆實現從入門運用和熟練掌握華爲應用內支付服務。git

IAP的啓用及商品管理方法

啓用服務

1.開啓服務開關github

1.1. 登陸 AppGallery Connect 網站,選擇「個人項目」,在項目列表中找到相應項目,在項目下的應用列表中選擇須要開通服務的應用。json

1.2. 在所選應用頁面中點擊<個人項目>,進入「API管理」頁籤,打開須要開通服務所在行的開關。api

*API管理:在此頁籤確認和開通所需服務能力。支持一個服務有多個API開關,可單獨控制各API開啓或關閉,也可經過該服務右側的API總開關一鍵開啓或關閉該服務下的所有API。安全

2.配置支付服務參數        
2.1. 在所選應用頁面左側導航欄選擇[盈利 > 應用內支付服務],點擊[設置]。
若是首次配置會彈出簽署協議彈框。服務器

2.2. 點擊「訂閱通知地址」後的「✔」圖標,配置訂閱通知地址。app

2.3. 配置完成點擊「✔」。ide

建立商品及沙盒測試

一、建立商品:全球訂價,一個版本搞定
登陸AppAallery Connect,在[個人應用]界面選擇應用,進入[運營]頁面。

*數據信息爲模擬

在[運營]頁面左側導航欄選擇[產品運營 > 商品管理],選擇[商品列表],點擊[添加商品]。

1.1相關參數說明:
①確認建立的商品類型:華爲應用內支付支持三種商品類型:
消耗型商品:便可以消耗使用的商品,好比遊戲類應用中的金幣、鑽石、點券等,可用來兌換和購買應用內虛擬服務和物品的貨幣。
非消耗型商品:即購買後永久使用的商品。好比遊戲中特別篇關卡、教育類應用中無限時的課程學習權限等
訂閱型商品:即一種預約支付方式,購買後將來一段時間內容許訪問增值更能或內容,週期結束後自動續期購買下一期的服務。好比音視頻類、教育課程類應用中的月度會員等。
根據你所需添加的商品屬性,來勾選商品類型進行進一步填寫。

②商品ID:以大小寫字母及數字開頭,由字母、數字及下劃線 (_) 和句點 (.)組成,字符輸入上限148個,一個應用內商品ID不能重複,保存後不能修改,刪除後沒法再次使用。

③語言:點擊[管理語言列表],勾選需支持的語言種類
④商品名稱:不能爲空,字符上限55個,不支持特殊字符|
⑤商品簡介:不能爲空,字符上限100個,不支持特殊字符|
⑥商品價格:點擊「查看編輯」,爲商品適配合適價格。

1.2 面向全球的商品信息設置:若是你的應用開發面向全球多個國家,無需維護管理多個地域版本。

多語言設置:建立商品時,在<語言>欄,勾選需支持的國家語言,並在<商品名稱>一欄,輸入對應國家語言的商品名及信息。

全球實時匯率訂價參考:不一樣國家及地域的價格設置,只需定義一個商品價格,不一樣國家和地區價格會根據當地匯率來計算並顯示,給開發者提供當地訂價依據,也可自定義單個地域訂價。

商品信息填寫完成後,點擊[編輯價格],便可進入商品價格編輯界面。

根據以上操做步驟,完成商品建立,回到商品列表頁面進行商品的狀態編輯,點擊<生效>,即添加商品完成。 

相關參數說明:

  • 通用幣種要求國家/地區:支持整數或兩位小數,如輸入1.34,則默認選取1.34做爲該商品的輸入價格;
  • 特殊幣種要求國家/地區:

- 僅支持整數的國家/地區,整數或向上取整做爲輸入價格,如輸入5.02,則默認選取6.00做爲該商品的輸入價格;
- 僅以五分之一爲最小單位的國家/地區,整數或向上取符合五分之一要求的數值做爲輸入價格,如輸入1.23,則默認選取1.40做爲該商品的輸入價格。

1.3商品激活
填寫好信息點擊[保存],返回商品列表,新添加商品會顯示處於[失效]狀態,點擊確認[激活],商品即生效。

二、客戶端接入,支付過程開發指南

2.1 判斷是否支持應用內支付

在使用應用內支付以前,你的應用需向華爲應用內支付發送isEnvReady請求,以此判斷用戶當前登陸的華爲賬號所在的服務地是否在華爲IAP支持結算的國家/地區中。若是應用未接入華爲賬號的登陸接口,可經過該接口完成登陸操做。

開發步驟以下:

發起isEnvReady請求,並設置兩個回調監聽來接收接口請求的結果。
•    當接口請求成功時,你的應用將獲取到一個IsEnvReadyResult實例對象,表示用戶當前登陸的華爲賬號所在的服務地支持IAP。
•    當接口請求失敗時,IAP會返回一個Exception對象,若該對象爲IapApiException對象,可以使用其getStatusCode()方法獲取這次請求的返回碼。
當返回賬號未登陸(OrderStatusCode.ORDER_HWID_NOT_LOGIN)時,可以使用IapApiException對象中的status拉起華爲賬號登陸頁面,此後在Activity的onActivityResult方法中獲取結果信息。從onActivityResult返回的intent中解析出returnCode,當returnCode=OrderStatusCode.ORDER_STATE_SUCCESS時,則表示當前賬號所在服務地支持IAP,其餘則表示這次請求有異常。
*Status不支持序列化,請勿對IAP接口返回的Status對象執行序列化操做。

// 獲取調用接口的Activity對象
final Activity activity = getActivity();
Task<IsEnvReadyResult> task = Iap.getIapClient(activity).isEnvReady();
task.addOnSuccessListener(new OnSuccessListener<IsEnvReadyResult>() {
    @Override
    public void onSuccess(IsEnvReadyResult result) {
        // 獲取接口請求的結果
        int accountFlag = result.getAccountFlag();
    }
}).addOnFailureListener(new OnFailureListener() {
    @Override
    public void onFailure(Exception e) {
        if (e instanceof IapApiException) {
            IapApiException apiException = (IapApiException) e;
            Status status = apiException.getStatus();
            if (status.getStatusCode() == OrderStatusCode.ORDER_HWID_NOT_LOGIN) {
                // 未登陸賬號
                if (status.hasResolution()) {
                    try {
                        // 6666是您自定義的常量
                        // 啓動IAP返回的登陸頁面
                        status.startResolutionForResult(activity, 6666);
                    } catch (IntentSender.SendIntentException exp) {
                    }
                }
            } else if (status.getStatusCode() == OrderStatusCode.ORDER_ACCOUNT_AREA_NOT_SUPPORTED) {
                // 用戶當前登陸的華爲賬號所在的服務地不在華爲IAP支持結算的國家/地區中
            }
        } else {
            // 其餘外部錯誤
        }
    }
});
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == 6666) {
        if (data != null) {
            // 使用parseRespCodeFromIntent方法獲取接口請求結果
            int returnCode = IapClientHelper.parseRespCodeFromIntent(data);
            // 使用parseAccountFlagFromIntent方法獲取接口返回的賬號類型
            int accountFlag = IapClientHelper.parseAccountFlagFromIntent(data);
        }
    }
}

2.2 展現商品信息

在華爲AppGallery Connect網站上完成商品的配置後,需在你的應用中使用obtainProductInfo接口來獲取此類商品的詳細信息。

開發步驟以下:

1.    構建請求參數ProductInfoReq,發起obtainProductInfo請求並設置OnSuccessListener和OnFailureListener回調監聽器以接收接口請求的結果。您須要在ProductInfoReq中攜帶您此前已在華爲AppGallery Connect網站上定義並生效的商品ID,並根據實際配置的商品指定其priceType。
*obtainProductInfo每次只能查詢一種類型的商品。
2.    當接口請求成功時,IAP將返回一個ProductInfoResult對象,您的應用可經過該對象的getProductInfoList方法獲取到包含了單個商品信息的ProductInfo對象的列表。您可使用ProductInfo對象包含的商品價格、名稱和描述等信息,向用戶展現可供購買的商品列表。

List<String> productIdList = new ArrayList<>();
// 查詢的商品必須是您在AppGallery Connect網站配置的商品
productIdList.add("ConsumeProduct1001");
ProductInfoReq req = new ProductInfoReq();
// priceType: 0:消耗型商品; 1:非消耗型商品; 2:訂閱型商品
req.setPriceType(0);
req.setProductIds(productIdList);
// 獲取調用接口的Activity對象
final Activity activity = getActivity();
// 調用obtainProductInfo接口獲取AppGallery Connect網站配置的商品的詳情信息
Task<ProductInfoResult> task = Iap.getIapClient(activity).obtainProductInfo(req);
task.addOnSuccessListener(new OnSuccessListener<ProductInfoResult>() {
    @Override
    public void onSuccess(ProductInfoResult result) {
        // 獲取接口請求成功時返回的商品詳情信息
        List<ProductInfo> productList = result.getProductInfoList();
    }
}).addOnFailureListener(new OnFailureListener() {
    @Override
    public void onFailure(Exception e) {
        if (e instanceof IapApiException) {
            IapApiException apiException = (IapApiException) e;
            int returnCode = apiException.getStatusCode();
        } else {
            // 其餘外部錯誤
        }
    }
});

2.3 發起購買

AppGallery Connect網站支持託管的商品包括消耗型商品,非消耗型商品和訂閱型商品。您的應用可經過createPurchaseIntent接口發起購買請求。開發步驟以下:
1.    構建請求參數PurchaseIntentReq,發起createPurchaseIntent請求。您須要在PurchaseIntentReq中攜帶您此前已在AGC網站上定義並生效的商品ID。當接口請求成功時,您可獲取到一個PurchaseIntentResult對象,其getStatus方法返回了一個Status對象,您的應用須要經過Status對象的startResolutionForResult方法來啓動華爲IAP收銀臺。
*Status不支持序列化,請勿對IAP接口返回的Status對象執行序列化操做。

// 構造一個PurchaseIntentReq對象
PurchaseIntentReq req = new PurchaseIntentReq();
// 經過createPurchaseIntent接口購買的商品必須是您在AppGallery Connect網站配置的商品。
req.setProductId("CProduct1");
// priceType: 0:消耗型商品; 1:非消耗型商品; 2:訂閱型商品
req.setPriceType(0);
req.setDeveloperPayload("test");
// 獲取調用接口的Activity對象
final Activity activity = getActivity();
// 調用createPurchaseIntent接口建立託管商品訂單
Task<PurchaseIntentResult> task = Iap.getIapClient(activity).createPurchaseIntent(req);
task.addOnSuccessListener(new OnSuccessListener<PurchaseIntentResult>() {
    @Override
    public void onSuccess(PurchaseIntentResult result) {
        // 獲取建立訂單的結果
        Status status = result.getStatus();
        if (status.hasResolution()) {
            try {
                // 6666是您自定義的常量
                // 啓動IAP返回的收銀臺頁面
                status.startResolutionForResult(activity, 6666);
            } catch (IntentSender.SendIntentException exp) {
            }
        }
    }
}).addOnFailureListener(new OnFailureListener() {
    @Override
    public void onFailure(Exception e) {
        if (e instanceof IapApiException) {
            IapApiException apiException = (IapApiException) e;
            Status status = apiException.getStatus();
            int returnCode = apiException.getStatusCode();
        } else {
            // 其餘外部錯誤
        }
    }
});

2. 在你的應用拉起收銀臺而且當用戶完成支付後(成功購買商品或取消購買),華爲IAP會經過onActivityResult方式將這次支付結果返回給應用。可以使用parsePurchaseResultInfoFromIntent方法獲取包含結果信息的PurchaseResultInfo對象。

•    當用戶購買成功時,可從PurchaseResultInfo對象中獲取到購買數據InAppPurchaseData及其簽名數據,您須要使用在華爲AppGallery Connect分配的公鑰進行簽名驗證,公鑰獲取和驗證方法請參見對返回結果驗籤
•    用戶購買消耗型商品時,若是返回如下支付異常則須要檢查是否存在掉單狀況,具體請參見消耗型商品的補單流程
•    支付失敗(OrderStatusCode.ORDER_STATE_FAILED)
•    已擁有該商品(OrderStatusCode.ORDER_PRODUCT_OWNED)

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == 6666) {
        if (data == null) {
            Log.e("onActivityResult", "data is null");
            return;
        }
        // 調用parsePurchaseResultInfoFromIntent方法解析支付結果數據
        PurchaseResultInfo purchaseResultInfo = Iap.getIapClient(this).parsePurchaseResultInfoFromIntent(data);
        switch(purchaseResultInfo.getReturnCode()) {
            case OrderStatusCode.ORDER_STATE_CANCEL:
                // 用戶取消
                break;
            case OrderStatusCode.ORDER_STATE_FAILED:
            case OrderStatusCode.ORDER_PRODUCT_OWNED:
                // 檢查是否存在未發貨商品
                break;
            case OrderStatusCode.ORDER_STATE_SUCCESS:
                // 支付成功
                String inAppPurchaseData = purchaseResultInfo.getInAppPurchaseData();
                String inAppPurchaseDataSignature = purchaseResultInfo.getInAppDataSignature();
                // 使用您應用的IAP公鑰驗證簽名
                // 若驗籤成功,則進行發貨
                // 若用戶購買商品爲消耗型商品,您須要在發貨成功後調用consumeOwnedPurchase接口進行消耗
                break;
            default:
                break;
        }
    }
}

2.4 確認交易

用戶完成一次支付以後,你需根據購買數據InAppPurchaseData的purchaseState字段來判斷訂單是否已成功支付。若purchaseState爲已支付(取值爲0),你需發放相應的商品或提供相應的服務,此後須要向華爲IAP發送發貨確認請求。

  •  對於消耗型商品,你需從InAppPurchaseData JSON字符串中解析出purchaseToken信息,用於確認商品的發貨狀態。在成功發貨並記錄已發貨的商品的purchaseToken以後,你的應用須要使用consumeOwnedPurchase接口消耗該商品,以此通知華爲應用內支付服務器更新商品的發貨狀態。發送consumeOwnedPurchase請求時,請在請求參數中攜帶purchaseToken。應用成功執行消耗以後,華爲應用內支付服務器會將相應商品從新設置爲可購買狀態,用戶便可再次購買該商品。
// 構造ConsumeOwnedPurchaseReq對象
ConsumeOwnedPurchaseReq req = new ConsumeOwnedPurchaseReq();
String purchaseToken = "";
try {
    // purchaseToken需從購買信息InAppPurchaseData中獲取
    InAppPurchaseData inAppPurchaseDataBean = new InAppPurchaseData(inAppPurchaseData);
    purchaseToken = inAppPurchaseDataBean.getPurchaseToken();
} catch (JSONException e) {
}
req.setPurchaseToken(purchaseToken);
// 獲取調用接口的Activity對象
final Activity activity = getActivity();
// 消耗型商品發貨成功後,需調用consumeOwnedPurchase接口進行消耗
Task<ConsumeOwnedPurchaseResult> task = Iap.getIapClient(activity).consumeOwnedPurchase(req);
task.addOnSuccessListener(new OnSuccessListener<ConsumeOwnedPurchaseResult>() {
    @Override
    public void onSuccess(ConsumeOwnedPurchaseResult result) {
        // 獲取接口請求結果
    }
}).addOnFailureListener(new OnFailureListener() {
    @Override
    public void onFailure(Exception e) {
        if (e instanceof IapApiException) {
            IapApiException apiException = (IapApiException) e;
            Status status = apiException.getStatus();
            int returnCode = apiException.getStatusCode();
        } else {
            // 其餘外部錯誤
        }
    }
});
  • 對於非消耗型商品,華爲應用內支付服務器默認返回已確認的訂單數據,在用戶購買成功以後無需確認交易。您須要在用戶購買成功以後持續向用戶提供相應的商品服務。具體請參見提供非消耗型商品對應的服務
  • 對於訂閱型商品,在用戶購買成功後,無需您額外執行確認交易操做,但須要在訂閱生效期間持續向用戶提供相應的商品服務,具體請參見訂閱專用功能說明-提供商品對應的服務

2.5提供非消耗型商品對應的服務

若你的應用爲用戶提供非消耗型商品,可在應用啓動時經過obtainOwnedPurchases接口獲取用戶已購的非消耗型商品的購買信息,格式請參見InAppPurchaseData。若返回的購買信息列表不爲空,請確認每一個購買信息的purchaseState字段。若purchaseState爲0,你需提供相應的商品服務。

開發步驟以下:

1.    使用obtainOwnedPurchases獲取用戶已購非消耗型商品的信息。
2.    你的應用須要在請求參數OwnedPurchasesReq中指定查詢的priceType爲1。
3.    你可從返回的每一個商品信息中解析出purchaseState,用於判斷當前商品的購買狀態,以此做爲你的應用的發貨標誌。

// 構造一個OwnedPurchasesReq對象
OwnedPurchasesReq ownedPurchasesReq = new OwnedPurchasesReq();
// priceType: 1:非消耗型商品
ownedPurchasesReq.setPriceType(1);
// 獲取調用接口的Activity對象
final Activity activity = getActivity();
// 調用obtainOwnedPurchases接口
Task<OwnedPurchasesResult> task = Iap.getIapClient(activity).obtainOwnedPurchases(ownedPurchasesReq);
task.addOnSuccessListener(new OnSuccessListener<OwnedPurchasesResult>() {
    @Override
    public void onSuccess(OwnedPurchasesResult result) {
        // 獲取接口請求結果
        if (result != null && result.getInAppPurchaseDataList() != null) {
            for (int i = 0; i < result.getInAppPurchaseDataList().size(); i++) {
                String inAppPurchaseData = result.getInAppPurchaseDataList().get(i);
                String inAppSignature = result.getInAppSignature().get(i);
                // 您須要使用您的應用的IAP公鑰驗證inAppPurchaseData的簽名
                // 若是驗籤成功,請檢查支付狀態
                try {
                    InAppPurchaseData inAppPurchaseDataBean = new InAppPurchaseData(inAppPurchaseData);
                    int purchaseState = inAppPurchaseDataBean.getPurchaseState();
                } catch (JSONException e) {
                }
            }
        }
    }
}).addOnFailureListener(new OnFailureListener() {
    @Override
    public void onFailure(Exception e) {
        if (e instanceof IapApiException) {
            IapApiException apiException = (IapApiException) e;
            Status status = apiException.getStatus();
            int returnCode = apiException.getStatusCode();
        } else {
            // 其餘外部錯誤
        }
    }
});

三、沙盒測試:無需真實支付,完成端到端支付測試

添加商品完畢後,可經過沙盒測試,在無需真實付款的條件下,進行端到端的支付環節檢測。沙盒測試步驟以下:

3.1配置沙盒測試環境

· 設置測試賬號。
在進行測試前,需在AppGallery Connect中的用戶與訪問添加測試賬號,這些測試賬號都是真實的華爲賬號。具體請參見<管理測試賬號>。

*說明:沙盒測試賬號添加完成以後須要30min~1h才能生效。使用時請檢查當前的賬號是否支持沙盒測試。

· 配置沙盒測試版本。
若是要測試的應用包此前沒有在AGC上架過版本,只須要確保測試包的versionCode大於0;若是已有上架的版本,則測試包的versionCode須要大於上架版本的versionCode。

3.2測試非訂閱型商品支付

可在設備上登陸已配置的測試賬號,並安裝該待測試應用。發起非訂閱型商品購買時,華爲IAP會檢測到該用戶爲測試用戶,跳過實際支付環節,直接支付成功。
效果示例以下:

3.3測試訂閱型商品續訂

訂閱型商品的購買流程和普通商品(非訂閱)的購買流程相似,但訂閱還有其餘細節場景,好比續訂成功或失敗,續訂週期時長。爲了幫助開發者快速測試應用的訂閱場景,沙盒環境下的訂閱續訂時間會比正常狀況更快,引入「時光機」概念。
時光機:僅針對訂閱型商品的續期時間,不影響訂閱型商品的生效時間(好比訂閱週期爲1周,商品在3分鐘後發生續期,此時訂閱型商品有效期延長了1周)。
效果示例以下:

完成以上步驟,你就順利搞定應用內商品建立和測試上架。商品管理環節,商品信息修改、失效激活商品、刪除商品等操做步驟,咱們下期繼續。
華爲應用內支付還提供了「零掉單」的訂單管理、提營收增加的加強型訂閱服務等能力,咱們也會在後期爲你們推送超細詳解,敬請期待哦~

瞭解更多詳情,請戳:

>>華爲應用內支付服務官網
>>獲取開發指導文檔
>>華爲移動服務開源倉庫地址:GitHubGitee

點擊右上角頭像右方的關注,第一時間瞭解華爲移動服務最新技術~

相關文章
相關標籤/搜索