2018年4月5日 星期四

[Windows][C++] User impersonation

最近在寫一個功能,想要對有 TLS 連線的 Process 檢查一下封包裡的憑證是不是有問題。好不容易實作出來了卻發現了一個問題。我寫的這個程式是跑在 System 身分裡的,如果說有用戶把憑證加入目前使用者的白名單,那程式透過 Cypto API 就會讀不到使用者加入的白名單憑證。這個問題可以用 User impersonation 來解決。

在 Windows 管理憑證中,每個使用者都會有自己的 Certificate Store, 當使用者一號把一張有問題的憑證加入自己 Store 白名單時,並不會影響其他的使用者仍判定這張憑證是有問題的。

搜尋 MMC ->  檔案 -> 新增/移除嵌入式管理元件 -> 新增"憑證",可以發現是針對用戶、或是本機來管理憑證

HANDLE hToken = NULL;
wchar_t szCurrentUserName[260] = {};

// 取得 PID 為 4612 的 handle
HANDLE processHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, 4612);

// 透過 handle 取得對應的 token
if (!OpenProcessToken(processHandle, TOKEN_ALL_ACCESS, &hToken))
{
    cout << "OpenProcessToken failed. GetLastError returned:" << GetLastError() << endl;
}
else
{
    // ImpersonateLoggedOnUser 模仿該使用者
    if (!ImpersonateLoggedOnUser(hToken))
    {
        cout << "ImpersonateLoggedOnUser Error" << endl;
    }
    else
    {
        // 印出 Current User Name 是否改成模仿的使用者名稱
        ZeroMemory(szCurrentUserName, sizeof(szCurrentUserName));
 nSize = ARRAYSIZE(szCurrentUserName);
 if (!GetUserName(szCurrentUserName, &nSize))
 {
            ReportError(L"GetUserName");
            goto Cleanup;
 }
        wprintf(L"The current user is %s\n\n", szCurrentUserName);

        // 試著讀取 Current User 裡的 My 憑證是否是改變成模仿的使用者
        HCERTSTORE hStore = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, NULL, CERT_SYSTEM_STORE_CURRENT_USER, L"My");
        PCCERT_CONTEXT pCertContext = NULL;
        while (pCertContext = CertEnumCertificatesInStore(hStore, pCertContext))
        {
            // 印出憑證裡的 Issuer 和 Subject (此為自定義的 Function)
            printCertContent(pCertContext);
        }
    }
}

最主要的重點在於 ImpersonateLoggedOnUser Function, 權限高的才能模擬權限低的,符合我 System 身分模擬一般使用者。

這邊附上微軟提供的 Demo 程式。

2015年7月1日 星期三

[iOS] Apple Push Notification Service - 實作 Server 端

在上一篇 [iOS] Google Cloud Messaging for iOS 完整說明了 iOS app 實作以及 GCM Server 的實作。這篇將補齊 APNS Server 端的實作方式。

  1. 製作 ck.pem 供 APNS Server 使用
    • 輸出 Push Notification Service 憑證 ([iOS] Google Cloud Messaging for iOS 有說明製作方法),儲存成 cert.p12
    • 展開 Push Notification Service 憑證,輸出該專用金鑰,儲存成 key.p12
    • 使用終端機並指到上列兩個檔案的儲存地,依序輸入下列指令
    • openssl pkcs12 -clcerts -nokeys -out cert.pem -in cert.p12
      
      openssl pkcs12 -nocerts -out key.pem -in key.p12
      
      cat cert.pem key.pem > ck.pem
      
    • 將 ck.pem 放置 Server 上
  2. 完整 PHP 程式碼
  3. // Put your device token here (without spaces):
    $deviceToken = 'iOS App 的 DeviceToken';
    
    // Put your private key's passphrase here:
    $passphrase = 'ck.pem 的金鑰';
    
    // Put your alert message here:
    $message = 'Hello APNS!';
    
    $ctx = stream_context_create();
    //stream_context_set_option($ctx, 'ssl', 'local_cert', 'ck.pem');
    stream_context_set_option($ctx, 'ssl', 'local_cert', 'ck.pem');
    stream_context_set_option($ctx, 'ssl', 'passphrase', $passphrase);
    
    // Open a connection to the APNS server
    $fp = stream_socket_client(
        'ssl://gateway.sandbox.push.apple.com:2195', $err,
        $errstr, 60, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT, $ctx);
    
    if (!$fp)
        exit("Failed to connect: $err $errstr" . PHP_EOL);
    
    echo 'Connected to APNS' . PHP_EOL;
    
    // Create the payload body
    $body['aps'] = array(
        'alert' => $message,
        'sound' => 'default'
    );
    
    // Encode the payload as JSON
    $payload = json_encode($body);
    
    // Build the binary notification
    $msg = chr(0) . pack('n', 32) . pack('H*', $deviceToken) . pack('n', strlen($payload)) . $payload;
    
    // Send it to the server
    $result = fwrite($fp, $msg, strlen($msg));
    
    if (!$result)
        echo 'Message not delivered' . PHP_EOL;
    else
        echo 'Message successfully delivered' . PHP_EOL;
    
    // Close the connection to the server
    fclose($fp);
    

2015年6月23日 星期二

[iOS] Google Cloud Messaging for iOS

2015 年 5 月底的 Google I/O 大會,提到 Google Cloud Messaging 也能使用於 iOS 了。而官方的教學文件也已經更新,我也順利完成成品。趁著記憶猶新,趕快紀錄一下。

準備:
  • 可執行 Xcode 的作業系統
  • GCM 需要實機測試,需要成為付費的 Apple 開發者帳號
撰寫程式碼前:
  1. 安裝 Cocoapods
    • 打開終端機,輸入 sudo gem install cocoapods
    • 等待下載和安裝完畢
  2. 將 Google SDK 加入 iOS app 
    • 建立一個 Xcode 專案,或者是現有專案
    • 在專案中新增一個名為 Podfile 的檔案,打開並填入下列文字
    • source 'https://github.com/CocoaPods/Specs.git'
      platform :ios, '8.1'
      pod 'Google/CloudMessaging'
      
    • 使用終端機並指向該專案目錄底下
    • 在終端機輸入 pod install
    • 該專案會產生新的 .xcworkspace 檔案,之後便透過此檔案來開啟專案
  3. 至 Apple Developer 網站註冊該專案專屬的 App ID
    • 遵循網站步驟填資料
    • Enable Service 需勾選 Push Notifications
  4. 產生 Apple Push Notification Service SSL 憑證
    • 打開 Mac OS 的鑰匙圈,選擇上列鑰匙圈存取 -> 憑證輔助程式 -> 從憑證授權要求憑證
    • 名稱建議填寫 Apple Push Notification (Development),以求辨識。選擇儲存到硬碟
    • 至 Apple Developer 網站編輯剛才建立的 App ID,在 Development SSL certificate 選擇 Create Certificate, 將前一步驟產生的 certSigningRequest 檔上傳。之後 Download 網站產生好的 Development SSL certificate. 點兩下執行。
    • 重複上列步驟將 Production SSL cerificate 也建立完成。Development 供開發時需要,Production 為上架時需要,之後 Google 需要這兩個憑證。
  5. 至 Apple Developer 網站建立 iOS Provisioning Profile
    • 通常一般專案不需要特定建立專屬的 Provisioning Profile, 但是推播服務需要。
    • 選擇 iOS App Development, 並接下執行,完成後 Download 並執行即可。Provisioning Profile 便會自動關聯 Xcode
  6. 輸出 Apple Push Notification 憑證
    • 在鑰匙圈的左邊類別選擇憑證,將第4步驟的 Development 和 Production SSL certificate 右鍵輸出
  7. 到 Google Developer 建立應用程式,並上傳第6步驟的兩個憑證
    • https://developers.google.com/cloud-messaging/ios/client 點擊 Get a Configuration File 按鈕
    • 輸入隨意的 App Name 以及 Xcode 專案的 iOS Bundle ID
    • 上傳兩個憑證至對應的 Development 和 Production
    • 成功之後便能下載 GoogleService-Info.plist 檔案
    • 頁面往下拉會看到 Server API Key 和 Sender ID, 需要記錄下來

  8. 將前一步驟的 GoogleService-Info.plist 檔案加到 Xcode 的每個 target 裡
    • 在 Xcode 專案按右鍵,點選 Add Files to ...
    • 選擇 GoogleService-Info.plist 
    • Add to targets 的欄位需全部勾選
到此處一切都佈署好了,接著便進入程式碼階段。
  1. 需先至 Apple Push Notification (APN) Service 註冊,取得 DeviceToken
    • 在 AppDelegate.m 的 didFinishLaunchingWithOptions 函式中加入下列程式碼
    • - (BOOL)application:(UIApplication *)application 
          didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
          UIUserNotificationType allNotificationTypes =  (UIUserNotificationTypeSound | 
                UIUserNotificationTypeAlert |  UIUserNotificationTypeBadge);
          UIUserNotificationSettings *settings =  [UIUserNotificationSettings 
                settingsForTypes:allNotificationTypes categories:nil];
          [[UIApplication sharedApplication] registerUserNotificationSettings:settings];
          [[UIApplication sharedApplication] registerForRemoteNotifications];
      }
    • 在相同的函式中,需要取得在 GoogleService-Info.plist 裡的 gcmSenderID. 程式碼寫法如下
    • NSError* configureError;
      [[GGLContext sharedInstance] configureWithError:&configureError];
      NSAssert(!configureError, @"Error configuring Google services: %@", configureError);
      _gcmSenderID = [[[GGLContext sharedInstance] configuration] gcmSenderID];
      
  2. 向 Google 註冊 DeviceToken 和 gcmSenderID
    • 當 App 成功向 APN Service 註冊時,會自動執行 didRegisterForRemoteNotificationsWithDeviceToken 函式,將 DeviceToken 與 gcmSenderID 向 Google 註冊
    • - (void)application:(UIApplication *)application
          didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
        [[GGLInstanceID sharedInstance] startWithConfig:[GGLInstanceIDConfig defaultConfig]];
        _registrationOptions = @{kGGLInstanceIDRegisterAPNSOption:deviceToken,
                                 kGGLInstanceIDAPNSServerTypeSandboxOption:@YES};
        [[GGLInstanceID sharedInstance] tokenWithAuthorizedEntity:_gcmSenderID
                                                            scope:kGGLInstanceIDScopeGCM
                                                          options:_registrationOptions
                                                          handler:_registrationHandler];
      }
  3. 上面的 _registrationHandler 用來處理向 Google 註冊後要執行的函式,於是我們必須先在 didFinishLaunchingWithOptions 裡先實作這個 handler.
    • 向 Google 註冊成功時,會取得 RegistrationToken, 這個 handler 會告訴我們 RegistrationToken 是什麼,透過此 RegistrationToken 才能讓 Server 發送推播
    [[GCMService sharedInstance] startWithConfig:[GCMConfig defaultConfig]];
      __weak typeof(self) weakSelf = self;
      // Handler for registration token request
      _registrationHandler = ^(NSString *registrationToken, NSError *error){
        if (registrationToken != nil) {
          weakSelf.registrationToken = registrationToken;
          NSLog(@"Registration Token: %@", registrationToken);
          [weakSelf subscribeToTopic];
          NSDictionary *userInfo = @{@"registrationToken":registrationToken};
          [[NSNotificationCenter defaultCenter] postNotificationName:weakSelf.registrationKey
                                                              object:nil
                                                            userInfo:userInfo];
        } else {
          NSLog(@"Registration to GCM failed with error: %@", error.localizedDescription);
          NSDictionary *userInfo = @{@"error":error.localizedDescription};
          [[NSNotificationCenter defaultCenter] postNotificationName:weakSelf.registrationKey
                                                              object:nil
                                                            userInfo:userInfo];
        }
      };
    
  4. 當 App 開啟時,接收訊息會執行 didReceiveRemoteNotification 函式,非實作必要
  5. - (void)application:(UIApplication *)application
        didReceiveRemoteNotification:(NSDictionary *)userInfo {
      NSLog(@"Notification received: %@", userInfo);
      // This works only if the app started the GCM service
      [[GCMService sharedInstance] appDidReceiveMessage:userInfo];
    
    }
    
  6. 記得需要將 App 開啟 Background Mode 的 Push Notification 服務
App Client 一般這樣做就行了,接下來是 Server 端的程式,我將用 PHP 來實作。
  1. 建立 Payload
    • 雖然是使用 Google GCM, 但推播發送的格式還是要按照 APN Service. 於是新版的 Google GCM 加入了相容於 APN Service 的格式
    • $registerIds 就是 App 取得的 RegistrationToken, 記得寫上去
    • $content = "GCM 推播測試!";
      $registerIds = array();
      array_push($registerIds, $registrationToken);
      
      $message = array(
       'body' => $content,
       'badge' => 3
      );
      $fields = array(
       'registration_ids' => $registerIds,
       'notification' => $message,
      );
      
  2. 傳送訊息
  3. $google_api_key = "從 Google Developer 取得的 Server API Key";
    $url = 'https://android.googleapis.com/gcm/send';
    $headers = array(
     'Authorization: key=' . $google_api_key,
     'Content-Type: application/json'
    );
    // Open connection
    $ch = curl_init();
    
    // Set the url, number of POST vars, POST data
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    
    // Disabling SSL Certificate support temporarly
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($fields));
    
    // result
    $result = curl_exec($ch);
    echo $result;
    // Close connection
    curl_close($ch); 
這樣就算是大功告成了,透過這樣的方式就能收到推播。基本上要使用 Google GCM for iOS 時,Apple Push Notification Service 的步驟也差不多完成了。只差 Server 端還要改一下。

使用 Google GCM 的好處是不管 Android 還是 iOS, 用同一套 GCM Server 就可以了,根據我的實驗,iOS 收到訊息可能會 delay 很久,但當我使用 APN Server 發送一次訊息後,之後再用 GCM Server 送出推播就能很快的讓 iOS 收到。覺得 delay 很久的朋友不妨試試。

Google 有釋出範例程式,想清楚的看程式碼的朋友可去下載學習

本篇參考資料皆來自 Google Cloud Messaging.




2015年2月16日 星期一

[Android] 讓不同螢幕尺寸與解析度顯示相同比例的UI

Android支援太多手機,造成開發者必須因應不同的手機螢幕設計UI.
同樣是4.3吋的螢幕,有800*480的解析度,也有1280*720的解析度。
又或者一樣是1280*720解析度,但螢幕尺寸有5.5吋,也有7吋的。
在這麼錯縱複雜的螢幕下,如何設計出清晰且正常比例的UI呢?
首先要釐清一些概念與名詞。

像素 (Pixel)
也稱畫素,螢幕呈現影像的最小單位,也可說是點陣圖的最小單位。
數位影像尺寸通常是以像素計算,例如: 48*48 pixel 就是一個正方形的影像大小。

因為像素是數位影像的最小單位,可以想像成一個像素就是一個點,
48*48 pixel就是長和寬都由48個點所組成的。如下圖:




螢幕尺寸
也就是我們大家熟知的幾吋幾吋,手機是4吋的,
電腦螢幕是22吋的,電視螢幕是32吋的。

像素與螢幕尺寸的關係
請問,48*48 pixel的影像到底有多大? 他是一個正方形,長與寬相同,
不過你能告訴我長寬是幾公分嗎? 或是幾英寸呢? 不知道。
前面提到像素是最小單位,最小單位總也有個長度吧?
你會發現找了一堆資料,沒有人跟你說一像素是多大,只會說是最小單位。
其實48*48 pixel的圖片,它可以是長寬10公分,也可以是1公分。
取決於像素和螢幕尺寸的關係。

現在有兩支手機如下:
手機A,4.3吋,解析度800*400
手機B,4.3吋,解析度1280*720

手機A,請問800*480是什麼單位?
其實就是像素 (Pixel), 我個人覺得我們濫用了解析度這個詞。
800*480就是Pixel單位,它說明4.3吋的手機螢幕中,長有800個像素填充,
寬有480個像素填充。手機B解析度則較高,
因為它的長有1280個像素填充,寬有720個像素填充。
你可以把像素想成點,這兩支手機螢幕尺寸都是4.3吋,
可是兩支手機填充的像素不同,同樣尺寸下,像素填充越多,
解析度就越高。

這兩支手機如果顯示一張圖48*48 pixel會怎麼樣呢?
手機A會比較大張,手機B會比較小張,請看下圖:

圖中的鉛筆都是48*48 pixel,同樣是4.3吋手機A和手機B,
手機A,800個點(Pixel)填滿4.3吋的長,
手機B,1280個點(Pixel)填滿4.3吋的長,
它們的長一樣,使得手機A的點(Pixel)比手機B的點還要大點。
也就是你比較容易用肉眼看到手機A的一格格的畫格 (Pixel),
所以同樣Pixel的圖,手機A會比較大張,手機B比較小張。
手機A解析度比較低,手機B解析度比較高。

PPI
800*400就是填充像素,我會說解析度800*400濫用,
是因為螢幕解析度跟螢幕尺寸與填充像素有關,而不是只和填充像素有關。
1280*720的解析度一定比800*400高嗎? 如果同樣的螢幕尺寸是對的,
但不同的螢幕尺寸就不一定了喔。PPI才是正確拿來描述解析度的,請看下圖:


此圖來自iPhone官方網站,從左至右為iPhone 6 Plus, iPhone 6, iPhone 5s, iPhone 5c.
可以發現iPhone 6和iPhone 5s的螢幕尺寸與像素皆不同,但解析度都是326 ppi.
ppi的公式為 √(像素長度2+像素寬度2)/ 螢幕尺寸 ,將iPhone 5s和iPhone 6套用公式如下:
  • iPhone 5s,(1136*1136 + 640*640) / 4 = 325.96
  • iPhone 6 ,(1334*1334 + 750*750) / 4.7 = 325.61
iPhone 6雖然填充像素增加,但螢幕尺寸也增加,所以與iPhone 5的解析度相同。
也因為兩支手機的解析度相同,所以使用同樣像素尺寸的icon即可。
例如iPhone 6使用128*128的圖片,iPhone 5s也是要使用128*128的圖片,
如此圖片就會在這兩支手機中有相同的顯示比例。
所以,PPI的值越大,需要的圖片要越大張
另外,DPI通常用於列印出版用,在螢幕的世界裡,DPI = PPI.

讓Android自行選擇適合的圖片大小
螢幕世界中,DPI = PPI ,由於官方文件都使用DPI,
所以在此段落我們一概使用DPI當作解析度,但意思跟PPI是一樣的。
寫過Android程式就知道透過不同的drawable資料夾可以支援多尺寸螢幕。
現在我們知道PPI才是決定圖片大小,而非螢幕尺寸,也非像素填充。
資料夾的命名應該如下:
  • drawable-ldpi
    • 約 120 dpi
  • drawable-mdpi
    • 約 160 dpi
  • drawable-hdpi
    • 約 240 dpi
  • drawable-xhdpi
    • 約 320 dpi
  • drawable-xxhdpi
    • 約 480 dpi
別再使用small, normal, large等螢幕尺寸來規劃圖片大小,
可參考Google的Supporting Multiple Screens.

資料夾間,圖片大小相對比例應該為何呢? Google也有提供說明如圖:
這些倍率是怎麼算出來呢? Android有自己的圖片單位Density-independent pixel(dp),
在layout中UI的單位建議使用dp,因為它會幫你算出正確的pixel大小,公式如下:
  • pixel = dp * (dpi / 160)
由此可見,當dpi = 160 (也就是mdpi)時,pixel = dp. 更大的dpi只要透過此公式,
便可算出需要比mdpi使用多大的比例的圖。

DPI與PPI的差別
PPI = Pixel Per Inch,每英吋使用多少像素,用於螢幕。
DPI = Dot Per Inch,每英吋使用多少點,用於印刷。

在螢幕的世界裡,最小單位就是像素,
也就是點(所以前面才提到可以想像成點)。
嚴格來說,在螢幕上解析度應該都要說PPI,
只是現在都混用,所以只能說在螢幕中,
PPI = DPI。

在印刷中,是以一英吋裡可以噴出幾滴油墨計算,
一滴油墨就是一個點,所以使用DPI作為單位。
當影像由螢幕要列印出來時,PPI和DPI是不同的。
因為印刷機的不同,一像素未必等於一滴油墨(點)。

結論
  • 螢幕解析度由PPI決定,而不是螢幕尺寸也不是像素填充
  • PPI越高,所需要的圖案也越大,才能顯示相同比例的UI
  • drawable應該要以ldpi, mdpi, hdpi來分類,而不是small, normal, large來分類
  • 螢幕使用PPI,印刷使用DPI,只有在螢幕世界中PPI = DPI.

2015年2月11日 星期三

[Android] In-App Billing/Purchases 實作與測試

關於Android In-App Billing的簡介請看此篇
本篇將著墨在開發者實作部分,
由於Google Play Billing Library中已經有一個很完整的範例程式。
所以本篇會直接拿來使用,並暫時不講解程式碼。
按照底下步驟做便能完成APK與Google Play Developer Console連動。
  1. 下載Google Play Billing Library
    • 打開Android SDK manager並下載安裝Google Play Billing Library
    • 路徑為: /{SDK路徑}/sdk/extras/google/play_billing
  2. 將範例程式Import到Eclipse當中
    • 在Eclipse中點擊File -> Import -> Android -> Existing Android Code Into Workspace
    • 範例程式路徑為: /{SDK路徑}/sdk/extras/google/play_billing/samples/TrivialDrive
  3. 將Package Name更名
    • 原先的Package為com.example.android.trivialdrivesample, 由於com.example.android會無法上傳至Google Play Developer Console. 所以需要先行更名。
  4. 前往Google Play Developer Console建立一個新的應用程式
    • 點擊"新增應用程式"
    • 填入標題
    • 倘若您無法進入此頁面,代表您沒有支付25美金的上架費
  5. 將此應用程式的Public Key複製起來
  6. 將剛才的Key填入程式碼當中
    • 在MainActivity.java中可以看到下列程式碼,將之取代成剛才的Key
    • String base64EncodedPublicKey = "CONSTRUCT_YOUR_KEY_AND_PLACE_IT_HERE";
  7. 此應用程式有三個內購項目,我們必須先記下他的名稱,才方便我們等下在Google Play Developer Console建立內購項目
    • 從程式碼可以得知"premium"是屬於非消耗品,"gas"屬於消耗品,"infinite_gas"屬於訂閱項目
    • // SKUs for our products: the premium upgrade (non-consumable) and gas (consumable)
      static final String SKU_PREMIUM = "premium";
      static final String SKU_GAS = "gas";
      
      // SKU for our subscription (infinite gas)
      static final String SKU_INFINITE_GAS = "infinite_gas";
  8. 將此Project輸出成signed的APK, 不能使用debug key.
    • 對專案按右鍵Android Tools -> Export Signed Application Package...
    • 到此處整個Project應該不會有任何錯誤,若有錯誤需先處理再做此步驟
  9. 上傳剛才的APK至Google Play Developer Console
    • 將APK上傳至ALPHA階段,才能作測試用
  10. 至應用程式內產品加入剛才的三項產品
    • 在API v3中,納入管理的產品和為納入管理的產品是一樣的
    • "premium"和"gas"為納入管理的產品,"infinite_gas"則為訂閱
    • 在訂閱項目中,有一個免費試用期,建議填寫7天,好方便測試
    • 將需要的資訊填好後,記得儲存並啟用
  11. 將其他的Google帳號加入測試帳號中,之後才能透過此帳號進行購買測試。
    • 您無法使用發佈此App的Google帳號進行內購測試
    • 被加入的帳號必須登陸信用卡資訊才能進行測試
  12. 接下來您可以決定是否要發佈,發佈前仍需要將其他資訊填好,直到發佈應用程式的按鈕亮起
    • 如果沒有發佈,第11步驟的測試帳號仍可測試內購已簽章的APK,為測試購買
    • 如果發佈,第11步驟的測試帳號的購買行為是測試購買,其他帳號則是真實購買
  13. 發佈後,將您的Android手機登入剛才加入測試的Google帳號,安裝Signed好的APK檔,便能測試購買
    • 發佈後,需要等待幾個小時才能順利購買,這期間您可能在購買過程中發生錯誤,包括"需要認證,請登入Google帳號"等錯誤訊息
    • 底下是此範例程式的執行結果
請注意,納入管理的產品屬於測試訂單,不會扣錢。
可是訂閱則是真實訂單,是會扣錢的,
所以前面才會提醒要填寫7天的免費試用期,
在測試的時候記得7天內要記得取消訂閱,
才不會被扣到款。
超過7天沒有取消就會被扣款了,
只能透過商家電子錢包進行退款。
而且Google Play屬於國外交易,
大部分的信用卡都會收取國外交易手續費的喔。

另外要提醒的是,
所有在Google Play Developer Console的動作,
都不會馬上生效,
需要等待幾個小時甚至一兩天才會生效。
建議每一個動作都要想清楚再做!

如果想要給別的朋友或帳號測試,
可以將signed的APK傳給別人並將帳號加入測試。
或者建立一個Google網上論壇,
並在Google Play Developer Console將此論壇加入至ALPHA測試人員也可。
如何使用Google網上論壇可參照此篇文章

針對測試購買的結論
  • 不發佈APP,只有測試帳號才能測試購買。發佈APP,所有帳號都能購買,只是測試帳號屬於測試購買,其他帳號屬於真實購買。
  • 對於測試帳號,納入管理的產品屬於測試購買,訂閱是真實購買,所以將訂閱設定試用期,可免去退款動作。
  • 因為發佈ALPHA版本並不會在Google Play上公開,所以建立Google網上論壇只是方便特定測試人員能在Google Play上下載,並非必要動作。
  • 所有在Google Play Developer Console的動作都不會馬上生效,請至少等待數個小時甚至一天。