BlogRight arrowTrends & insightsRight ArrowStoreKit 2 API có gì mới và cách Apple đơn giản hoá việc tích hợp mua trong ứng dụng
BlogRight arrowTrends & insightsRight ArrowStoreKit 2 API có gì mới và cách Apple đơn giản hoá việc tích hợp mua trong ứng dụng

StoreKit 2 API có gì mới và cách Apple đơn giản hoá việc tích hợp mua trong ứng dụng

StoreKit 2 API có gì mới và cách Apple đơn giản hoá việc tích hợp mua trong ứng dụng
Listen to the episode
StoreKit 2 API có gì mới và cách Apple đơn giản hoá việc tích hợp mua trong ứng dụng

pple đã ra mắt phiên bản StoreKit 2 mới tại WWDC 2021 diễn ra gần đây. Đây là khung quản lý mua hàng trong iOS. Tỷ lệ các ứng dụng có tính năng gói đăng ký và mua trong ứng dụng (in-app purchase) tăng trưởng ổn định và Apple đã đơn giản hóa việc tích hợp tính năng mua trong ứng dụng vào ứng dụng với việc phát hành StoreKit 2. Hôm nay, chúng ta sẽ tìm hiểu về làm việc với StoreKit 2 trên một phần của máy chủ, nói cách khác, với sự trợ giúp của App Store Server API.

Xác thực yêu cầu

Trong phiên bản API hiện tại, bạn cần Khóa bí mật chia sẻ (Shared Secret) để gửi yêu cầu. Khóa này là một chuỗi cố định bí mật mà bạn có thể nhận được trong App Store Connect. Phiên bản API mới sử dụng tiêu chuẩn JSON Web Token (JWT) để xác thực yêu cầu.   

Tạo khóa 

Đầu tiên, tạo một khóa riêng tư được sử dụng để cho phép các yêu cầu. Mở App Store Connect và đến phần Users and Access, sau đó đến tab Keys. Chọn loại khóa In-App Purchase. Tải xuống khóa mới. Bạn cũng sẽ cần ID của khóa đó - bạn có thể sao chép ID này trên cùng một trang với Issue ID trong tab App Store Connect API.

Creating a private key to work with StoreKit 2
Tạo khóa riêng tư để làm việc với App Store Server API

Tạo mã token

Bước tiếp theo là tạo một mã token được sử dụng để cho phép các yêu cầu. Quá trình này được mô tả chi tiết trong tài liệu, vì vậy bạn không cần bận tâm quá nhiều. Dưới đây là một ví dụ về việc triển khai có sẵn cho Python. Cần lưu ý rằng bạn không cần thiết tạo một mã token mới cho mọi yêu cầu mới. Khi tạo mã token, bạn đặt thời gian tồn tại của mã là tối đa 60 phút và sử dụng mã token đó trong khoảng thời gian này. 

import time, uuid
from authlib.jose import jwt

BUNDLE_ID = 'com.adapty.sample_app'
ISSUER_ID = '4336a124-f214-4d40-883b-6db275b5e4aa'
KEY_ID = 'J65UYBDA74'
PRIVATE_KEY = '''
-----BEGIN PRIVATE KEY-----
MIGTAgMGByqGSMBHkAQQgR/fR+3Lkg4...
-----END PRIVATE KEY-----
'''

issue_time = round(time.time())
expiration_time = issue_time + 60 * 60 # 1 hour expiration


header = {
 'alg': 'ES256',
 'kid': KEY_ID,
 'typ': 'JWT'
}

payload = {
 'iss': ISSUER_ID,
 'iat': issue_time,
 'exp': expiration_time,
 'aud': 'appstoreconnect-v1',
 'nonce': str(uuid.uuid4()),
 'bid': BUNDLE_ID
}

token_encoded = jwt.encode(header, payload, PRIVATE_KEY)
token_decoded = token_encoded.decode()

authorization_header = {
 'Authorization': f'Bearer {token_decoded}'
}

Giao dịch đã ký

Trong phiên bản API mới, tất cả các giao dịch được trả về theo tiêu chuẩn JSON Web Signature (JWS). Tiêu chuẩn này là một chuỗi bao gồm ba phần được phân cách bởi dấu chấm.  

  1. Tiêu đề Base64.
  2. Dữ liệu truyền đi của giao dịch Base64.
  3. Chữ ký giao dịch.
Base64(header) + "." + Base64(payload) + "." + sign(Base64(header) + "." + Base64(payload))

Tiêu đề giao dịch

Cần có tiêu đề để đảm bảo giao dịch được xác thực. Khóa Alg chứa một thuật toán mã hóa, khóa x5c chứa một chuỗi chứng chỉ.

{
  "kid": "AMP/DEV",
  "alg": "ES256",
  "x5c": [
    "MIIEO...",
    "MIIDK..."
  ]
}

Dữ liệu truyền đi của giao dịch

{
  "transactionId": "1000000831360853",
  "originalTransactionId": "1000000806937552",
  "webOrderLineItemId": "1000000063561721",
  "bundleId": "com.adapty.sample_app",
  "productId": "basic_subscription_1_month",
  "subscriptionGroupIdentifier": "27636320",
  "purchaseDate": 1624446341000,
  "originalPurchaseDate": 1619686337000,
  "expiresDate": 1624446641000,
  "quantity": 1,
  "type": "Auto-Renewable Subscription",
  "appAccountToken": "fd12746f-2d3a-46c8-bff8-55b75ed06aca",
  "inAppOwnershipType": "PURCHASED",
  "signedDate": 1624446484882,
  "offerType": 2,
  "offerIdentifier": "basic_subscription_1_month.pay_as_you_go.3_months"
}

Apple đã thay đổi và mở rộng định dạng giao dịch. Theo tôi thì bây giờ, làm việc với các định dạng đó sẽ thuận tiện hơn. Bạn có thể tìm hiểu chi tiết về một định dạng mới trong tài liệu. Sau đây, tôi sẽ mô tả những thay đổi quan trọng nhất.    

  • Apple đã thêm trường appAccountToken, chứa ID người dùng của hệ thống. ID này phải ở định dạng UUID, định dạng này được thiết lập trong ứng dụng di động khi giao dịch mua đang được khởi tạo. Nếu định dạng này được thiết lập, tất cả các giao dịch sẽ được trả về ở định dạng này trong chuỗi này (gia hạn, vấn đề về thanh toán, v.v.) và bạn sẽ dễ dàng biết được người dùng đã thực hiện giao dịch mua.    
  • Apple cũng đã thêm các trường offerType và offerIdentifier chứa thông tin về ưu đãi đã sử dụng (nếu có). Dưới đây là các giá trị cho trường offerType:
  • 1 — ưu đãi ra mắt (intro offer) (chỉ có sẵn cho những người dùng không có gói đăng ký đang hoạt động hoặc đã hết hạn)
  • 2 — ưu đãi khuyến mãi (promo offer) (chỉ có sẵn cho các gói đăng ký hiện tại và đã hết hạn)
  • 3 — mã ưu đãi

Nếu ưu đãi khuyến mãi hoặc mã ưu đãi đã được sử dụng, khóa offerIdentifier sẽ chứa ID của ưu đãi đã sử dụng. Trước đây, không thể theo dõi việc sử dụng ưu đãi ở phía máy chủ, điều này khiến số liệu phân tích xấu đi. Bây giờ, bạn có thể sử dụng mã ưu đãi để phân tích.   

  • Apple đã thêm trường inAppOwnershipType, giúp biết được liệu người dùng đã mua sản phẩm hay truy cập sản phẩm đó nhờ gói đăng ký gia đình. Những giá trị khả thi:
  • PURCHASED
  • FAMILY_SHARED
  • Một trường mới khác - loại - bao gồm loại giao dịch. Những giá trị khả thi:
  • Gói đăng ký tự động gia hạn
  • Không tiêu thụ được
  • Tiêu thụ được
  • Gói đăng ký không tự động gia hạn
  • Các trường cancellation_date và cancellation_reason hiện có tên mới: revocationDate và revocationReason. Xin nhắc lại, các trường này chứa ngày tháng và lý do hủy bỏ gói đăng ký do được hoàn tiền, vì vậy tên mới trông hợp lý hơn.
  • Tất cả các khóa trả về ở định dạng camelCase (giống như trong tất cả các yêu cầu App Store Server API).
  • Tất cả ngày được hiển thị ở định dạng dấu thời gian Unix tính bằng mili giây.     

Trạng thái gói đăng ký của người dùng

Để kiểm tra trạng thái gói đăng ký của người dùng hiện tại, gửi một yêu cầu GET tới https://api.storekit.itunes.apple.com/inApps/v1/subscriptions/ {originalTransactionId}, trong đó {originalTransactionId} là ID của bất kỳ chuỗi giao dịch nào của người dùng. Đổi lại, bạn sẽ nhận được các giao dịch với trạng thái cho mọi nhóm đăng ký.   

{
  "environment": "Sandbox",
  "bundleId": "com.adapty.sample_app",
  "data": [
    {
      "subscriptionGroupIdentifier": "39636320",
      "lastTransactions": [
        {
          "originalTransactionId": "1000000819078552",
          "status": 2,
          "signedTransactionInfo": "eyJraWQiOi...",
          "signedRenewalInfo": "eyJraWQiOi..."
        }
      ]
    }
  ]
}

Khóa tình trạng hiển thị trạng thái gói đăng ký hiện tại, dựa vào đó, bạn có thể quyết định xem mình có nên cung cấp cho người dùng quyền truy cập vào các tính năng trả phí của ứng dụng hay không. Những giá trị khả thi:

  • 1 — gói đăng ký đang hoạt động, người dùng phải truy cập được các tính năng trả phí.
  • 2 — gói đăng ký đã hết hạn, người dùng không được phép truy cập các chức năng trả phí.  
  • 3 — trạng thái của gói đăng ký là Thử lại thanh toán (Billing Retry), nghĩa là người dùng không hủy gói đăng ký nhưng gặp sự cố khi thanh toán. Apple sẽ cố gắng trừ tiền trong thẻ trong 60 ngày. Người dùng không được phép truy cập các chức năng trả phí.  
  • 4 — trạng thái của gói đăng ký là Thời gian gia hạn (Grace Period), nghĩa là người dùng không hủy gói đăng ký đó nhưng gặp sự cố thanh toán. Thời gian gia hạn đang hiện hữu trong App Store Connect, vì vậy người dùng phải truy cập được các tính năng trả phí.    
  • 5 — gói đăng ký đã bị hủy do việc hoàn tiền, người dùng không được phép truy cập các chức năng trả phí. 

Khóa SignedTransactionInfo chứa thông tin về giao dịch cuối cùng trong chuỗi. Bạn có thể tìm thấy chi tiết về định dạng của khóa này ở trên.

Start for free

Integrate in-app subscriptions in your Android app in 30 minutes with all side cases

Start for free

Thông tin về việc gia hạn gói đăng ký (subscription renewal)  

Khóa SignedRenewalInfo chứa thông tin gia hạn gói đăng ký.

{
  "expirationIntent": 1,
  "originalTransactionId": "1000000819078552",
  "autoRenewProductId": "basic_subscription_1_month",
  "productId": "basic_subscription_1_month",
  "autoRenewStatus": 0,
  "isInBillingRetryPeriod": false,
  "signedDate": 1624520884048
}

Thông tin này cho phép chúng tôi biết được điều sẽ xảy ra với gói đăng ký trong kỳ thanh toán tiếp theo. Ví dụ: nếu bạn thấy người dùng đã hủy tự động gia hạn, bạn có thể đề nghị họ chuyển sang gói đăng ký khác hoặc cung cấp cho họ một ưu đãi khuyến mãi. Thật thuận tiện khi theo dõi loại sự kiện này với sự trợ giúp của thông báo máy chủ (server notifications) mà tôi sẽ cho bạn biết ngay sau đây.  

Lịch sử giao dịch của người dùng

Để lấy lịch sử giao dịch của người dùng, gửi yêu cầu GET tới https://api.storekit.itunes.apple.com/inApps/v1/history/{originalTransactionId}, trong đó {originalTransactionId} là ID của bất kỳ chuỗi giao dịch nào của người dùng. Đổi lại, bạn sẽ nhận được một loạt các giao dịch được sắp xếp theo thời gian.     

{
	"revision": "1625872984000_1000000212854038",
	"bundleId": "com.adapty.sample_app",
	"environment": "Sandbox",
	"hasMore": true,
	"signedTransactions": [
		"eyJraWQiOiJ...",
		"joiRVMyNeyX...",
		"5MnkvOTlOZl...",
		...
	]
}

Một yêu cầu không được chứa nhiều hơn 20 giao dịch. Nếu người dùng có số lượng giao dịch lớn hơn, giá trị của cờ hasMore sẽ là true. Nếu bạn cần trang giao dịch tiếp theo, hãy gửi lại yêu cầu có chứa thông số GET sửa đổi. Nó sẽ chứa giá trị từ cùng một khóa.  

Thông báo giao dịch máy chủ

Thông báo máy chủ giúp nhận thông tin về các giao dịch mua, gia hạn, các vấn đề về thanh toán mới, v.v. Điều này giúp xây dựng số liệu phân tích chính xác hơn, cũng như quản lý trạng thái của người đăng ký dễ dàng hơn.   

Các thông báo máy chủ hiện có (V1) có thể giải quyết hầu hết các vấn đề, nhưng đôi khi các thông báo này khá bất tiện. Hầu hết, đó là về tình huống khi bạn nhận được nhiều thông báo chỉ cho một hành động của người dùng. Ví dụ: bây giờ, khi người dùng nâng cấp gói đăng ký, Apple sẽ gửi hai thông báo: DID_CHANGE_RENEWAL_STATUS và INTERACTIVE_RENEWAL. Để xử lý trường hợp này, bạn hiện cần lưu trạng thái bằng cách nào đó và kiểm tra xem thông báo thứ hai đã được gửi chưa. Trong phiên bản thông báo máy chủ mới (V2), chỉ có một thông báo cho một hành động của người dùng. Cách này thuận tiện hơn nhiều.   

Phiên bản thông báo máy chủ thứ hai có các sự kiện mới – OFFER_REDEEMED, EXPIRED và GRACE_PERIOD_EXPIRED. Các sự kiện này giúp quản lý trạng thái người đăng ký dễ dàng hơn nhiều. Sự kiện SUBSCRIBED và PRICE_INCREASE  là những sự kiện được cải tiến từ phiên bản đầu tiên.

Transaction notifications in StoreKit 2 feature new events

Các loại thông báo

Thông báo hiện có nhiều loại, do đó, một thông báo cho bất kỳ hành động nào của người dùng là đủ để hiểu chuyện xảy ra. 

Notifications in StoreKit 2 have types

Các loại thông báo

{
  "notificationType": "SUBSCRIBED",
  "subtype": "INITIAL_BUY",
  "version": 2,
  "data": {
    "environment": "Sandbox",
    "bundleId": "com.adapty.sample_app",
    "appAppleId": 739104078,
    "bundleVersion": 1,
    "signedTransactionInfo": "eyJraWQiOi...",
    "signedRenewalInfo": "eyJraWQiOi..."
  }
}

Thông báo máy chủ chứa thông tin về một giao dịch và một lần gia hạn ở định dạng JWS mà tôi đã mô tả trước đó.  

Làm việc với môi trường Sandbox  

Bạn cần sử dụng URL của môi trường Sandbox để kiểm tra các giao dịch mua: https://api.storekit-sandbox.itunes.apple.com.

Hiện chưa có phiên bản mới của thông báo máy chủ để kiểm tra. Khi có phiên bản mới, bạn có thể chỉ định các URL khác nhau cho các thông báo Production và Sandbox. Bạn có thể chọn V2 cho Sandbox và V1 cho Production để kiểm tra. 

Ngoài ra, App Store Connect hiện cho phép:

  • Xóa lịch sử mua cho người dùng Sandbox, nghĩa là bạn không phải tạo tài khoản mới để làm điều đó nữa.   
  • Thay đổi quốc gia của cửa hàng cho người dùng Sandbox.
  • Thay đổi thời gian gia hạn gói đăng ký Sandbox, chẳng hạn như bạn có thể thực hiện giao dịch mua hàng tháng kéo dài 1 giờ thay vì 5 phút.     

Kết luận 

Apple đã cải thiện đáng kể cách làm việc với các gói đăng ký và mua trong ứng dụng ở phía máy chủ. Theo tôi thì đây là các tính năng mới hữu ích nhất:

  • Cung cấp khuyến mãi chính thức và cung cấp hỗ trợ mã;  
  • Thông báo máy chủ đơn giản hơn và nhiều thông tin hơn;   
  • Cơ hội để tìm hiểu về trạng thái của gói đăng ký hiện tại bằng một lệnh gọi API đơn giản;
  • Xóa lịch sử mua Sandbox của người dùng.   

Việc chuyển sang một API mới sẽ không khó, bạn chỉ cần nhận được originalTransactionId cho mỗi biên nhận là đủ. Có thể biên nhận đã có trong cơ sở của bạn.    

Dù sao, phần khó nhất của việc tích hợp gói đăng ký vào ứng dụng di động là xây dựng hệ thống phân tích và tối ưu hóa nền kinh tế. Adapty có thể giải quyết tốt những vấn đề này:

  • Phân tích tích hợp sẵn giúp chúng ta nhanh chóng hiểu được các chỉ số chính của ứng dụng.
  • Phân tích theo nhóm giúp nhận biết liệu có bất kỳ vấn đề nào với khía cạnh kinh tế.
  • Thử nghiệm A/B (A/B testing) giúp tăng lợi nhuận của ứng dụng.   
  • Tích hợp với các hệ thống bên ngoài cho phép gửi các giao dịch đến các dịch vụ phân bổ và phân tích sản phẩm.    
  • Các chiến dịch khuyến mãi giúp giảm bớt lượng suy giảm người dùng.   
  • SDK nguồn mở cho phép tích hợp gói đăng ký vào ứng dụng trong vòng vài giờ.  

Tìm hiểu thêm về các tính năng này, để triển khai gói đăng ký vào ứng dụng của bạn nhanh hơn và kiếm tiền từ ứng dụng sớm hơn và hiệu quả hơn.

Further reading

Monetizing an app with B2B sales?
Monetizing an app with B2B sales?
August 9, 2021
10 min read, 33 min listen
Adapty April Update: Segmentations, Offer Codes, Family Sharing
Adapty April Update: Segmentations, Offer Codes, Family Sharing
May 12, 2021
3 min read
Android in-app purchases, part 1: configuration and adding to the project
Android in-app purchases, part 1: configuration and adding to the project
January 27, 2021
14 min read