KOMOJU Android SDKとFirebaseを活用した決済デモの紹介

Share

はじめに

こんにちは!この記事では、KOMOJU Android SDKを使用してAndroidアプリ内に決済機能を実装する方法について、デモアプリを通して説明します。また、途中Cloud Functions for Firebaseを使用してデモアプリ用の簡単なバックエンドを構築します。

基本用語/サービス

用語定義参考リンク
サーバーレス関数クラウドプロバイダーによって管理された環境で、サーバーの管理を意識することなく実行できるコード。Cloud Functions for Firebase
Cloud Functions for Firebaseイベント駆動型のサーバーレス関数をデプロイして実行するためのサービス。Cloud Functions for Firebase
カスタムURLスキーム アプリ固有のURLスキーム。特定のアプリを外部から起動したり、Webからアプリに戻ったりするためのカスタムプロトコル。 Android アプリリンクの処理
Jetpack Compose KotlinでAndroidアプリのUIを宣言的に作成するためのツールキット。従来のXMLではなく、コードでUIを構築し、状態に応じて自動的に更新される仕組みを提供する。 Jetpack Composeの基本

内容一覧

この記事では、KOMOJU Android SDKを簡単に体験していただくためのデモアプリ通して次の内容を説明します:

  1. KOMOJUのAPI キーを取得
  2. Cloud Functions for Firebaseに関数を設定
  3. デモアプリの決済に関わる実装の解説

1. KOMOJUのAPI キーを取得

まず、KOMOJUから非公開鍵と公開鍵を取得します。
KOMOJUのダッシュボードの「設定」でAPIのセクションから以下のような値を取得できます。

sk_******
pk_******

非公開鍵はKOMOJUのSessionリソースを作成する際に使用し、
公開鍵はそのSessionの支払いやセキュアトークン作成などに使用します。

Sessionについて詳しくはこちら
認証について詳しくはこちら

2. Cloud Functions for Firebaseに関数を設定

次に、デモアプリからのリクエストを処理するためのサーバーとして、関数をCloud Functions for Firebaseに設定します。
ローカルに環境変数を定義する環境変数ファイル(.env)とエントリーポイントとなるindex.jsを用意します。

komapp_server/functions/.env
komapp_server/functions/index.js

ここで解説するデモで使用されているindex.jsのコードの全体はこちら

  • 環境変数ファイル (.env):
    非公開鍵、公開鍵、リターンURLを設定。(デモではカスタムスキームを komapp:// と設定しています。)
				
					SECRET_KEY=sk_******
PUBLISHABLE_KEY=pk_******
RETURN_URL=komapp://
				
			
  • エンドポイント定義 (index.js):
    デモサーバーでは以下2つのエンドポイントを定義しています。
 
    • 公開鍵を取得するためのエンドポイント (/serve-key)
      .envに定義した公開鍵を返します。
				
					app.get('/:serve-key', (req, res) => res.send({
    publishableKey: process.env.PUBLISHABLE_KEY
}));
				
			
    • Sessionを作成しIDを取得するためのエンドポイント(/create-session)
      KOMOJUのSession APIへリクエストしてSessionリソースを作成し、そのIDを返します。
      createSessionにはSession作成のためのKOMOJU Session APIのエンドポイント、非公開鍵、支払い完了後アプリに戻るためのreturn_urlが設定されています。
				
					app.post("/:create-session", async (req, res) => {
    const {
        amount,
        currency,
        language
    } = req.body;
    const secretKey = process.env.SECRET_KEY;
    try {
        const sessionId = await createSession({
            amount,
            currency,
            language,
            secretKey,
        });
        res.send({
            sessionId
        });
        console.log(sessionId)
    } catch (error) {
        res.status(500);
        res.send({
            error: error.message
        })
    }
});

async function createSession({
    amount,
    currency,
    language,
    secretKey
}) {
    if (!secretKey) {
        throw new Error("Secret Key Required");
    }

    const url = "https://komoju.com/api/v1/sessions";
    const options = {
        method: "POST",
        headers: {
            accept: "application/json",
            "content-type": "application/json",
            Authorization: `Basic ${Buffer.from(secretKey + ":").toString("base64")}`,
            "X-KOMOJU-API-VERSION": "2024-07-15",
        },
        body: JSON.stringify({
            default_locale: language ?? "ja",
            amount,
            currency,
            return_url: process.env.RETURN_URL,
        }),
    };
    const response = await fetch(url, options);
    if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
    }
    const {
        id
    } = await response.json();
    return id;
}

				
			
  • 関数をデプロイ:
    これらのファイルを用意したら、Cloud Functions for Firebaseへデプロイします。
    Firebaseプロジェクトを作成する必要があるので、以下の手順を参考に構成します。

    参考:
    GitHub – KOMOJU Mobile SDK Server
    Firebase Functions Get Started

    デプロイが成功すると、FirebaseコンソールのFunctionsからHTTPトリガーURLを取得できます。

3. デモアプリの決済に関わる実装の解説

ここからはデモAndroidアプリのうち、決済に関連するコードの要点解説を行います。
デモのコードの全体はこちら

  • ベースURLの設定:
    まず、Cloud FuncstionsのエンドポイントにリクエストできるようにベースURLを設定します。
    プロジェクト内にlocal.properties ファイルを作成し、Firebaseコンソールから取得したHTTPトリガーURLを入力します。
    デモのdev フレーバーではTEST_SERVER_URLをベースURLとして参照しています。
				
					## local.properties
TEST_SERVER_URL=https://***.run.app
				
			
  •  カスタムURLスキームの設定とSDKのインストール:
    build.gradleでカスタムURLスキームと依存関係を追加しています。
				
					android {
    defaultConfig {
	....
        resValue("string", "komoju_consumer_app_scheme", "komapp")
	....
    }
}

				
			
これはSDKが komoju_consumer_app_scheme というキーを使ってアプリのカスタムスキームを参照しているので、アプリ側でそのキーに値(ここではkomapp)を設定しています。
外部のサイトで決済後、アプリに戻る仕組みを構築しています。
				
					dependencies {
    implementation("com.komoju.mobile.sdk:android:<latest-version-here>")
....
}

				
			
dependenciesにはKOMOJU Android SDKを指定します。 デモではプロジェクト内にSDKが含まれているので、以下のように追加されています。
				
					dependencies {
    implementation(project(":komoju-android-sdk"))
....
}

				
			
  • インターフェースの定義:
    次は、Cloud Functionsに設定したエンドポイントへリクエストを行うためのインターフェースを見てみましょう。
    デモではRemoteApiServiceというインターフェースを定義しています。
				
					

interface RemoteApiService {
    @Headers("Content-Type: application/json", "Accept: application/json")
    @GET("serve-key")
    suspend fun getPublishableKey(): Response<PublishableKeyResponse>

    @Headers("Content-Type: application/json", "Accept: application/json")
    @POST("create-session")
    suspend fun createSession(@Body request: CreateSessionRequest): Response<CreateSessionResponse>

    companion object {
        fun create(): RemoteApiService = Retrofit.Builder()
            .client(
                OkHttpClient.Builder()
                    .connectTimeout(100, TimeUnit.SECONDS) // Set the connection timeout to 100 seconds because the glitch server is slow
                    .readTimeout(100, TimeUnit.SECONDS) // Set the read timeout to 100 seconds because the glitch server is slow
                    .build(),
            )
            .addConverterFactory(retrofit2.converter.gson.GsonConverterFactory.create())
            .baseUrl(BuildConfig.SERVER_URL)
            .build()
            .create(RemoteApiService::class.java)
    }
}
				
			
Cloud Functionsに設定した2つのエンドポイントに対応するメソッドが定義されています。
    • getPublishableKey():
      GETメソッドで”serve-key”エンドポイントにリクエストを送ります。公開鍵を取得します。
    • createSession():
      POSTメソッドで”create-session”エンドポイントにリクエストを送ります。Sessionを作成しそのIDを取得します。

RetrofitはHTTPリクエストを作成・送信するためのライブラリで、
OkHttpClientを使用して接続タイムアウトや読み込みタイムアウトを100秒に設定しています。

baseUrlはAPIのベースURLを指定しています。(ここではlocal.propertiesに設定したURLが渡されることになります)
  • エンドポイントが呼ばれるタイミング:
    次にそれぞれのエンドポイントが呼ばれるタイミングについて見ていきます。
    • デモアプリ起動時に公開鍵を取得しています。
				
					    private suspend fun init() {
        publishableKey = fetchPublishableKey()
        loadItems()
    }

				
			
    • 購入ボタンをタップした時にSessionを作成しています。
      • 購入するアイテム(item)に基づいて、CreateSessionRequest オブジェクトを作成し、アイテムの価格、通貨、言語の情報を含めてサーバーに送信します。
      • Cloud Functionsを介してKOMOJUからSession IDを受け取ると、それを KomojuAndroidSDK.Configuration.Builder に渡し、KomojuAndroidSDK.Configuration オブジェクトを構成します。
				
					    fun onBuyClicked(item: Item) {
        screenModelScope.launch {
            createSession(item)?.let { sessionId ->
                KomojuAndroidSDK.Configuration.Builder(
                    requireNotNull(publishableKey),
                    sessionId,
                ).setLanguage(language)
                    .setCurrency(currency)
                    .setConfigurableTheme(komojuConfigurableTheme)
                    .setInlinedProcessing(true)
                    .build().let { komojuConfig ->
                        _komojuSDKConfiguration.value = komojuConfig
                    }
            }
        }
    }

    private suspend fun createSession(item: Item): String? = runCatching {
        _uiState.update { it.copy(isCreatingSession = true) }
        remoteApiService.createSession(
            request = CreateSessionRequest(
                amount = item.price.toInt(),
                currency = currency.currencyCode,
                language = language.languageCode,
            ),
        ).body()?.sessionId
    }.onSuccess { sessionId ->
        if (sessionId == null) {
            _uiState.update { it.copy(isCreatingSession = false, error = "Failed to fetch session id.") }
        } else {
            _uiState.update { it.copy(isCreatingSession = false) }
        }
    }.onFailure { error ->
        _uiState.update {
            it.copy(
                isCreatingSession = false,
                error = "Failed to fetch session id.\n" + "${error.message}",
            )
        }
    }.getOrNull()
				
			
  • 決済SDKを起動するランチャー:
    最後に以下の決済SDKを起動するランチャーのポイントを見てみます。
    • rememberLauncherForActivityResult
      非同期で決済結果を待機:
      Jetpack ComposeのrememberLauncherForActivityResultを使って、決済SDKの結果を非同期で受け取る準備をします。このランチャーは、決済が完了した後にKomojuAndroidSDK.activityResultContractを基に結果を処理します。

      決済結果の処理:
      結果が返されると、rememberLauncherForActivityResult のコールバック内で処理が行われます。 it.isSuccessFul で決済が成功したかどうかを判定し、それに応じて成功画面(FakeOrderSuccessScreen)または失敗画面(FakeOrderFailedScreen)に遷移します。

    • LaunchedEffect
      LaunchedEffect で komojuSDKConfigurationの変更を監視し、設定情報が変更されると(つまりSession IDを受け取ると)決済画面が起動します。
				
					        val komojuPaymentLauncher = rememberLauncherForActivityResult(KomojuAndroidSDK.activityResultContract) {
            screenModel.onKomojuPaymentCompleted()
            navigator.push(if (it.isSuccessFul) FakeOrderSuccessScreen() else FakeOrderFailedScreen())
        }

        LaunchedEffect(komojuSDKConfiguration) {
            val configuration = komojuSDKConfiguration
            if (configuration.canProcessPayment()) {
                komojuPaymentLauncher.launch(configuration)
            }
        }
				
			

決済に関わる実装の解説は以上になります。
もちろんこの通りにしなければいけないということはありませんが、実装の際の参考になれば幸いです。

デモアプリ自体はサーバー(Cloud Functions)の準備と/local.propatiesファイルにURLの記述が終われば、以下を実行しビルドすることですぐにテスト決済をお試しいただけます。

./gradlew :example-android:assembleDev
./gradlew installDevDebug

支払い完了後にKOMOJUダッシュボードを見るとテスト決済が完了していることが分かります。

今回もお読みくださりありがとうございました!次回もお楽しみに。

Stay up to date

Sign up to receive the latest news to your email

Payment methods

All Payment Methods