當用戶在應用市場裏下載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周)。
效果示例以下:
完成以上步驟,你就順利搞定應用內商品建立和測試上架。商品管理環節,商品信息修改、失效激活商品、刪除商品等操做步驟,咱們下期繼續。
華爲應用內支付還提供了「零掉單」的訂單管理、提營收增加的加強型訂閱服務等能力,咱們也會在後期爲你們推送超細詳解,敬請期待哦~
瞭解更多詳情,請戳:
>>華爲應用內支付服務官網
>>獲取開發指導文檔
>>華爲移動服務開源倉庫地址:GitHub、Gitee
點擊右上角頭像右方的關注,第一時間瞭解華爲移動服務最新技術~