🍎

【Appleサブスクリプションオファー】プロモーションオファー署名の作成方法

2022/12/04に公開

Appleのサブスクリプションオファーのプロモーションオファーに関しての情報がなかった。
これが、日本初の資料。

プロモーションオファーとは

WWDC 2019動画

https://developer.apple.com/videos/play/wwdc2019/305

Apple公式ドキュメント

https://developer.apple.com/jp/app-store/subscriptions/#subscription-offers

7ec9cdbd-1f10-4e9a-a5a5-c76e2c99f9ee.png

対象となるのは、そのサブスクリプションを現在利用している、または過去に利用したことがあるお客様です。
これらのオファーによって、ユーザー数の拡大や維持のため、
独自のプロモーションを柔軟に行うことができるようになります。
キャンペーンを通じて、サブスクリプションをキャンセルした利用者に再サブスクリプションを促したり、
別のサブスクリプションへのアップグレードを特別価格で提供したりすることができます。

準備

Apple公式ドキュメント

サブスクリプションオファーの設定

https://developer.apple.com/jp/documentation/storekit/in-app_purchase/setting_up_subscription_offers/

実装

※今回は、サーバーサイド(Ruby)での署名作成に関してのみ記載する

Apple公式ドキュメント

プロモーションオファー用の署名の生成
https://developer.apple.com/jp/documentation/storekit/in-app_purchase/generating_a_signature_for_subscription_offers/

署名の生成に必要なもの

スクリーンショット 2020-09-10 12.16.24.png

■appBundleID

環境変数で持つ

■keyIdentifier

環境変数で持つ

■productIdentifier

アプリ側からパラメータでもらう

■offerIdentifier

アプリ側からパラメータでもらう

■applicationUsername

アプリ側からパラメータでもらう

■nonce

サーバー側で生成する

■timestamp

サーバー側で生成する

署名

スクリーンショット 2020-09-10 12.45.19.png

署名の生成

サンプルコード (Ruby)
本来はメソッドで分割するが、わかりやすさを重視する

require 'openssl'
require 'base64'
require 'securerandom'
require 'json'

# 環境変数として読み込むが、あえて記載する
private_key = '-----BEGIN PRIVATE KEY-----xxxxxxxxxxxxxxxxxxx-----END PRIVATE KEY-----'

# 環境変数から読み込んだ秘密鍵の改行コードがエスケープされてしまうのを防ぐ
private_key = OpenSSL::PKey::EC.new(private_key.gsub(/\\n/, "\n"))

app_bundle_id = 'xxxx'
key_identifier = 'xxxx'
product_identifier = 'xxxx'
offer_identifier = 'xxxx'
application_username = 'xxxx'
nonce = SecureRandom.uuid
timestamp = (Time.current.to_f * 1000).to_i.to_s

# 不可視の分離文字('\u2063')をパラメータの間にはさみ、結合する

payload = app_bundle_id + "\u{2063}" +
          key_identifier + "\u{2063}" +
          product_identifier + "\u{2063}" +
          offer_identifier + "\u{2063}" +
          application_username + "\u{2063}" +
          nonce + "\u{2063}" +
          timestamp

# 署名

# Ruby2.4.0以降であれば
# signature = private_key.sign(digest, data)

# SHA-256ハッシュを使って署名
signature = private_key.dsa_sign_asn1(OpenSSL::Digest::SHA256.digest(payload))

# base64にエンコード
# strict_encode64を使い、改行コードを消す
signature_base64 = Base64.strict_encode64(signature)


# 検証

# OpenSSL::PKey::ECオブジェクトを生成
ec = OpenSSL::PKey::EC.new(private_key.group)
ec.public_key = private_key.public_key

# SHA-256ハッシュで検証
digest = OpenSSL::Digest::SHA256.new

# payloadを秘密鍵で署名したその署名文字列がsignatureであることを公開鍵を使って検証
ec.verify(digest, signature, payload)

result = { key_identifier: key_identifier, nonce: nonce, timestamp: timestamp, signature: signature_base64 }.to_json

楕円曲線デジタル署名アルゴリズム(ECDSA)について

最初はruby_ecdsaというgemを使おうかと検討していた
https://github.com/DavidEGrayson/ruby_ecdsa

require 'ecdsa'
require 'securerandom'
require 'digest/sha2'

group = ECDSA::Group::Secp256k1

private_key = 1 + SecureRandom.random_number(group.order - 1)
public_key = group.generator.multiply_by_scalar(private_key)

message = 'ECDSA is cool.'
digest = Digest::SHA2.digest(message)

temp_key = 1 + SecureRandom.random_number(group.order - 1)
signature = ECDSA.sign(group, private_key, digest, temp_key)

valid = ECDSA.valid_signature?(public_key, digest, signature)
puts "valid: #{valid}"

が、秘密鍵が数値を想定したつくりになっていたので使用を見送った

Apple公式の署名作成サンプル

JavaScriptとNode.jsを使ったサンプル

https://developer.apple.com/documentation/storekit/in-app_purchase/subscriptions_and_offers/generating_a_subscription_offer_signature_on_the_server

サーバを起動して、アクセスするとレスポンスが返る

Discussion