Tana Gone
Tana Gone
1 min read

Categories

アプリを開発してマネタイズする方法の一つにActivation Codeの販売を行うのがある。その際のバックエンド、フロントエンドの実装を調べてみた。なお、以下に掲載するコードの内、Code生成、Codeの正当性検証のコード(Swift, Ruby)以外は実際に動作を確認していない。

  • 開発者の口座に入金がなされる仕組み

    Activation Codeは決済の終えた購入者にメールで送るのだが、決済の部分は代行サービスStripeを利用することにする。購入者は決済代行サービス(以下Stripe)のアカウントを保有している必要はない。メールアドレス、クレカの番号を入力した後、購入ボタンが押されればStripeが手数料を引いた代金をアプリ開発者の口座に振り込んでくれるのである。 購入ボタンを含むフォームはStripeが提供するページに設置することも出来るし、開発者のWebサイト(アプリ販売サイト)に埋め込む事もできる。開発者のWebサイトでは購入ボタンを押下された後にやってくるPOSTデータを受信してだれが購入したのかを知ることになる。Webサイトには次の情報は来ない。

    • カード番号
    • カードブランド
    • 有効期限
    • CVCコード

    以下のコードはRubyのSinatraで構築されたサイトでPOSTデータを受け取る部分を抜粋したものである。 埋め込みコンポーネント | Stripe のドキュメント

    require 'stripe'
    
    post '/create-checkout-session' do
      price_id = params[:price]
      quantity = params[:quantity].to_i  # 必要に応じて型変換
      
      session = Stripe::Checkout::Session.create(
        payment_method_types: ['card'],
        line_items: [{
          price: price_id,
          quantity: quantity,
        }],
        mode: 'subscription',
        success_url: 'https://yourapp.com/success',
        cancel_url: 'https://yourapp.com/cancel'
      )
      
      redirect session.url, 303
    end
    
  • POSTデータを送信するフォーム fetchメソッドの送信先はSinatraのPOSTデータハンドラーに設定しておく事に注意。

    <script src="https://js.stripe.com/v3/"></script>
    <script>
      const stripe = Stripe("pk_test_XXXXXXXXXXXXX"); // 公開鍵
      
      document.getElementById("checkout-button").addEventListener("click", function () {
        fetch("/create-checkout-session", {
          method: "POST"
        })
        .then(response => response.json())
        .then(session => {
          return stripe.redirectToCheckout({ sessionId: session.id });
        })
        .catch(error => console.error("Error:", error));
      });
    </script>
      
    <button id="checkout-button">購入する</button>
    
  • 開発者の口座への振込タイミング 必ずしも月末ではありません。Stripeの振込タイミングは国や業種、アカウントの状態によって異なります:

🏦 条件 振込タイミングの例
通常の新規アカウント(日本) 約7日サイクル(初回は最大14日)
信用実績があるアカウント 即日~翌営業日振込が可能になるケースも
カスタムアカウント設定 自分で振込スケジュールの設定も可能

たとえば、日本の一般的なアカウントでは、毎週金曜に振込という設定が初期値になっていたりします。

  • WebhookエンドポイントをSinatraサイトに作っておけば決済完了を非同期に検知でき、Checkout セッションの ID(session_id)顧客ID(customer)支払い成功イベントのWebhookペイロード(※この中にカードブランドが入ることはある)を受け取れる。

  • Webhookのペイロードから session.customer_emailsession.client_reference_id を使って、購入者のメールアドレスを取得。メールにActivation Codeを記載して送信すれば購入者は購入手続きを完了できる。

  • Activation Codeの作成方法は、メールアドレスのHash値を算出し先頭3バイトを6文字のHexとすれば良い。生成されたコードの検証コードをアプリに埋め込めばアプリのActivationを実行できる。Swift、Rubyで作った生成、検証コードは次の通り

    // crypto2.swift
    import Foundation 
    import  CryptoKit 
    // (Hash-based Message Authentication Code))
    let ALen = 6
    let BLen = 3
    func isActivationCodeValid(_ prefix: String, _ code: String) -> Bool {
        guard code.count == ALen else { return false }
          
        // let prefix = code.prefix(3))
        // let signature = String(code.suffix(3))
      
        let key = SymmetricKey(data: Data("AAA000".utf8))
        let hash = HMAC<SHA256>.authenticationCode(for: Data(prefix.utf8), using: key)
        let expectedSignature = Data(hash.prefix(BLen)).map { String(format: "%02X", $0) }.joined().prefix(ALen)
      
        return code == expectedSignature
    }
      
    func generateActivationCode(from prefix: String) -> String {
        let key = SymmetricKey(data: Data("AAA000".utf8))
        let hash = HMAC<SHA256>.authenticationCode(for: Data(prefix.utf8), using: key)
        let signature = Data(hash.prefix(BLen)).map { String(format: "%02X", $0) }.joined().prefix(ALen)
        return  String(signature)
    }
    let prefix = "foo@bar.com".uppercased()
    let a = generateActivationCode(from: prefix)
    let b = isActivationCodeValid(prefix, a)
    print(prefix, a, b)  // => FOO@BAR.COM 409FC6 true
    // Sun, 13 Jul 2025 22:25:30 +0900
    // メールアドレスと秘密鍵からHASHを生成して先頭3バイト(6文字Hex)を取り出しActivation Codeとする。
    // 有効性は同じくメールアドレスと秘密鍵から6文字Hexを生成しActivation Codeと同じかを判定。
      
      
    

    Ruby版

    require 'openssl'# cryptoRuby2.rb
      
    ALen = 6
    BLen = 3
    SECRET = "AAA000"
      
    def generate_activation_code(prefix)
      raise "prefix must be at least #{BLen} characters" unless prefix.size >= BLen
      data = prefix[0, BLen].upcase
      
      hmac = OpenSSL::HMAC.digest("sha1", SECRET, data)
      signature = hmac.unpack("H*").first[0, ALen].upcase # hex文字列から先頭6桁取得
      
      signature
    end
      
    def activation_code_valid?(prefix, code)
      return false unless code.size == ALen
      data = prefix[0, BLen].upcase
      
      hmac = OpenSSL::HMAC.digest("sha1", SECRET, data)
      expected = hmac.unpack("H*").first[0, ALen].upcase
      
      code == expected
    end
      
    prefix = "tana00@example.com"
    code = generate_activation_code(prefix) # => Generated Code: BF7721
    puts "Generated Code: #{code}"
    puts activation_code_valid?(prefix, code) # => true
    
    • 🧾 Stripe以外の代表的な決済代行サービス
サービス名 特徴 決済手数料の目安
Stripe Stripeは特に開発者向けのAPIが充実していて、柔軟なカスタマイズやサブスクリプション課金、マーケットプレイス型の決済にも対応できるのが強み 3.6%
Square 対面・オンライン両対応。端末が安価で導入しやすい 約2.5〜3.25%
PayPal 世界的に普及。個人利用にも強い 約3.6%+固定手数料
GMOペイメントゲートウェイ 大手企業向け。多機能で柔軟なカスタマイズが可能 約3.5%+処理料
Airペイ iPadと連携。店舗向けに人気 約2.48〜3.24%
楽天ペイ 楽天経済圏との連携が強み 約2.2〜3.24%
STORES決済 小規模事業者向け。月額無料プランあり 約1.98〜3.24%