Mười hai giờ là tất cả thời gian Apple cho bạn để biện minh cho một khoản hoàn tiền bằng chứng cứ. Khoảng thời gian đó trở nên vô nghĩa nếu giao dịch trước mắt bạn thuộc về một khách hàng mà bạn không thể xác định tên. appAccountToken là UUID cung cấp cái tên đó, và nó chỉ có thể được gắn vào trong lúc giao dịch mua đang diễn ra.
Hãy hình dung thông báo vừa đến. Apple muốn biết người mua này thực sự đã sử dụng bao nhiêu phần sản phẩm, họ đã là khách hàng bao lâu, và tổng số tiền họ đã chi. Đây là những câu hỏi hợp lý, và cơ sở dữ liệu của bạn chứa mọi câu trả lời. Vấn đề là payload trước mắt bạn chỉ cung cấp một originalTransactionId và một mã định danh sản phẩm, không cái nào trong hai thứ này tương ứng với bất cứ thứ gì trong bảng người dùng của bạn. Bạn đang cầm một hóa đơn không có tên trên đó, trong khi các câu trả lời bạn cần lại được lưu trữ theo tên.
Sự không khớp đó có một cách khắc phục, và cách khắc phục đó có một thời hạn. appAccountToken là một UUID do hệ thống của bạn tạo ra, chuyển cho StoreKit trong lúc thanh toán, và Apple sau đó sẽ đóng dấu nó vào giao dịch được tạo thành. Từ thời điểm đó, mọi thông tin Apple báo cho bạn về giao dịch mua đó đều đến kèm sẵn danh tính. Tuy nhiên, nếu bỏ lỡ thời điểm đó, giao dịch bán hàng đang nói đến sẽ vẫn ẩn danh vĩnh viễn. Dưới đây: trường này là gì, cách gắn nó, điểm cuối (endpoint) cho phép cứu lại hồi tố các giao dịch mua mà ứng dụng của bạn chưa từng thấy, và phép tính về những gì một khoản hoàn tiền ẩn danh thực sự lấy đi khỏi doanh nghiệp của bạn.
Những điểm chính cần nắm
Gắn một UUID vào một giao dịch mua trên App Store thì giao dịch được tạo ra sẽ hướng thẳng trở lại một người dùng cụ thể trong hồ sơ của bạn. Apple giữ giá trị đó trên giao dịch và trả lại nó ở mọi nơi giao dịch đó xuất hiện.
Định dạng là điều duy nhất Apple kiểm soát, và định dạng đó là UUID. Nếu bạn đưa vào một id cơ sở dữ liệu, một địa chỉ email, hay một chuỗi được ghép lại, StoreKit sẽ loại bỏ nó mà không báo lỗi, để lại nil ở nơi lẽ ra phải là appAccountToken.
StoreKit 2 cung cấp nó dưới dạng một tùy chọn mua duy nhất, Product.PurchaseOption.appAccountToken(_:), được cấp bằng một UUID mà hệ thống của bạn đã tạo ra và lưu lại cho tài khoản đó từ trước.
Gắn nó vào giao dịch mua đầu tiên và Apple sẽ lan truyền chính giá trị đó qua mọi lượt gia hạn, mọi lần thử lại thanh toán, và mọi lượt nâng cấp trong suốt chuỗi đăng ký đó.
Từ năm 2025, điểm cuối (endpoint) Set App Account Token cho phép máy chủ của bạn gắn thẻ cho các giao dịch mua mà luồng trong ứng dụng chưa từng thấy, trong đó có việc đổi mã ưu đãi và các giao dịch mua được quảng bá.
CONSUMPTION_REQUEST của Apple chỉ có thể được trả lời nhờ vào token này. Nếu thiếu nó, bạn không thể kết nối khoản hoàn tiền với chính khách hàng mà bạn được yêu cầu phải mô tả chi tiết mức sử dụng trong khung thời gian 12 giờ.
Một khoản hoàn tiền không xác định được danh tính là một khoản hoàn tiền không thể bảo vệ. Tiền sẽ bị trả lại cho những giao dịch bán hàng mà bạn từng có bằng chứng, và cùng với đó là chi phí tính toán (compute), các lệnh gọi API, và các khoản thanh toán đã tiêu tốn để cung cấp chúng.
Trường này thực sự làm gì
Apple coi giá trị này là vô nghĩa, và đó chính là điểm mấu chốt. Bất kể UUID nào bạn đưa cho StoreKit trong lúc thanh toán sẽ được ghi vào giao dịch và lưu giữ vô thời hạn, mà không có bất kỳ nỗ lực nào để diễn giải nó, xác thực nó tham chiếu đến điều gì, hay kiểm tra xem nó có phân giải thành bất cứ thứ gì hay không. Định nghĩa của chính Apple gọi nó là "UUID liên kết giao dịch với tài khoản của người dùng trên dịch vụ của riêng bạn." Việc diễn giải hoàn toàn thuộc về bạn. Apple chỉ đơn giản mang theo giá trị đó và trả lại nó khi được yêu cầu.
Nơi nó được trả lại chính là điều làm cho trường này trở nên hữu ích. Các thông báo máy chủ mang theo nó trong giao dịch đã ký. Yêu cầu Get Transaction Info từ App Store Server API và nó sẽ xuất hiện ở đó. Mọi lượt gia hạn tiếp theo trong chuỗi đăng ký cũng sẽ tái tạo lại nó, với điều kiện giao dịch mua ban đầu có mang theo nó. Nói cách khác, gắn giá trị đó vào một thời điểm, và nó sẽ tiếp tục xuất hiện trở lại trong suốt khoảng thời gian khách hàng đó còn tiếp tục trả tiền cho bạn.
Truyền vào bất cứ thứ gì không phải UUID và nó sẽ biến mất mà không có cảnh báo
Các nhà phát triển thường phát hiện ra quy tắc này bằng cách vi phạm nó. Một thứ gì đó tưởng chừng hợp lý được truyền vào, chẳng hạn một id tài khoản nội bộ, một địa chỉ email, hay vài trường được nối lại với nhau, và StoreKit không hề phản đối gì cả. Không có ngoại lệ, không có cảnh báo, không có giao dịch mua thất bại. Giao dịch đơn giản chỉ trả về với appAccountToken ở trạng thái nil, và liên kết mà bạn nghĩ mình đã có thực ra chưa bao giờ được tạo ra. Các diễn đàn dành cho nhà phát triển của Apple liên tục có những thắc mắc giống hệt kiểu này, thường được diễn đạt là "appAccountToken is missing in the transaction payload", và lời giải thích hầu như luôn là một giá trị không đạt yêu cầu về định dạng.
Yêu cầu đó rất hẹp: StoreKit 2 chỉ chấp nhận một UUID theo chuẩn RFC 4122 và không gì khác. Vì vậy, cách làm an toàn là tạo UUID trên máy chủ của bạn, lưu lại nó gắn với tài khoản, và đảm bảo không có loại giá trị nào khác có thể lọt vào lệnh gọi mua hàng.
Sự im lặng chính là điều khiến điều này nguy hiểm. Một giá trị sai định dạng không gây ra lỗi nào và không làm thất bại giao dịch bán hàng nào, nó chỉ làm mất đi liên kết đó, bị loại bỏ một cách im lặng trong khi mọi thứ khác vẫn diễn ra bình thường. Kiểm tra giá trị theo định dạng UUID trước khi nó đến StoreKit chỉ tốn một dòng mã và loại bỏ hoàn toàn kiểu lỗi này.
Gắn token trong lúc thanh toán
Toàn bộ cách triển khai chỉ cần một tùy chọn mua. Tạo UUID ở phía máy chủ khi đăng ký, hoặc bất cứ khi nào khách hàng lần đầu đến trang thanh toán, ghi nó vào bản ghi tài khoản của họ, rồi cung cấp đúng giá trị đó mỗi lần họ mua hàng.
Chữ ký của tùy chọn này là Product.PurchaseOption.appAccountToken(_ token: UUID), và lệnh gọi thực tế trông như try await product.purchase(options: [.appAccountToken(token)]). Bất kể giao dịch hoàn tất đến với bạn theo cách nào, cho dù bạn tự xác minh nó qua App Store Server API hay nhận nó dưới dạng một thông báo máy chủ, UUID đó luôn đi kèm bên trong nó, điều này giúp việc xác định khách hàng chỉ còn là một lượt tra cứu theo chỉ mục duy nhất.
Một token cho mỗi khách hàng, sử dụng lại vĩnh viễn
Một sai lầm dễ mắc phải là tạo một token mới cho mỗi giao dịch. Làm vậy sẽ âm thầm phá vỡ lợi ích của token. Apple sẽ tái tạo lại bất kể token nào đã có trên giao dịch mua ban đầu trong suốt các lượt gia hạn, các lần thử lại thanh toán, và các lượt nâng cấp trong chuỗi đó, nghĩa là một UUID cố định cho mỗi tài khoản sẽ giữ cho toàn bộ lịch sử thanh toán của khách hàng luôn được liên kết. Thay đổi giá trị đó và mỗi giao dịch mua sẽ trở thành một hòn đảo riêng biệt. Tính ổn định là điều quan trọng ở đây, vì vậy hãy chọn một UUID khi tài khoản được tạo và không bao giờ thay đổi nó.
Điểm cuối cứu lại những giao dịch mua mà ứng dụng của bạn chưa từng động đến
Trước đây, có hai hình thức mua hàng hoàn toàn vô hiệu hóa cơ chế này. Việc đổi mã ưu đãi diễn ra trong App Store, không phải trong ứng dụng của bạn. Việc mua một giao dịch mua trong ứng dụng được quảng bá từ một trang niêm yết trên store cũng vậy. Cả hai hình thức này đều không thực thi đoạn mã mua hàng của bạn, nghĩa là không có thời điểm nào để cung cấp token, và các giao dịch đó mãi mãi ẩn danh.
Hạn chế đó đã chấm dứt tại WWDC 2025 với điểm cuối Set App Account Token. Máy chủ của bạn gửi một yêu cầu PUT /inApps/v1/transactions/{originalTransactionId}/appAccountToken đến App Store Server API, mang theo UUID trong phần body, và Apple sẽ ghi nó vào giao dịch. Phạm vi áp dụng trải rộng trên mọi loại sản phẩm, bao gồm cả việc đổi mã ưu đãi và các giao dịch mua được quảng bá, và một giá trị được gửi theo cách này sẽ thay thế bất kể token nào mà giao dịch đã mang theo trước đó. Do đó, việc xác định danh tính đã trở thành điều bạn có thể thực hiện sau đó, hoàn toàn ở phía máy chủ, đối với các giao dịch mua mà ứng dụng của bạn chưa từng tham gia.
Nơi bạn gắn token phụ thuộc vào cách giao dịch mua đã diễn ra:
Đối với một giao dịch mua trong ứng dụng thông thường, hãy thiết lập nó trong tùy chọn mua của StoreKit ngay tại thời điểm mua, thông qua Product.PurchaseOption.appAccountToken(UUID).
Đối với việc đổi mã ưu đãi, hãy thiết lập nó bằng điểm cuối Set App Account Token từ máy chủ của bạn, vì không có gì trong ứng dụng để gắn vào và bạn phải gắn nó vào sau đó.
Đối với một giao dịch mua trong ứng dụng được quảng bá từ App Store, hãy thiết lập nó theo cách tương tự bằng điểm cuối Set App Account Token, vì giao dịch diễn ra bên ngoài ứng dụng của bạn.
Đối với một lượt gia hạn đăng ký, bạn không cần làm gì cả. Token được kế thừa tự động từ giao dịch mua ban đầu.
Một liên kết bị đứt gãy gây ra điều gì cho doanh thu của bạn
Vấn đề ở đây không phải là sự gọn gàng trong việc lưu hồ sơ. Vấn đề là Apple hỏi các nhà phát triển đúng một câu hỏi trong quá trình hoàn tiền, được gửi dưới dạng CONSUMPTION_REQUEST, và để trả lời câu hỏi đó cần phải biết khách hàng nào trong số khách hàng của bạn đang được nói đến.
Các yêu cầu hoàn tiền đối với hàng hóa tiêu dùng (consumables) và các gói đăng ký không tự động gia hạn sẽ kích hoạt thông báo đó, và Send Consumption Information là cách bạn phản hồi, trong vòng mười hai giờ. Apple mong đợi các thông tin cụ thể về một cá nhân: tỷ lệ sản phẩm đã sử dụng, thời gian là khách hàng, tổng chi tiêu trọn đời, trạng thái giao hàng. Token xuất hiện như một trường trong chính yêu cầu đó, nhưng vai trò quan trọng hơn của nó mang tính cấu trúc, vì nó là thứ duy nhất có khả năng biến thông báo đó thành một dòng dữ liệu trong cơ sở dữ liệu của chính bạn. Loại bỏ token và yêu cầu đó sẽ trở nên không thể trả lời được, bất kể việc theo dõi sử dụng của bạn kỹ lưỡng đến đâu.
Cái giá của việc trả lời bằng sự trống rỗng
Chỉ còn lại hai lựa chọn khi một khoản hoàn tiền đến mà không xác định được danh tính, và không lựa chọn nào tốt cả. Gửi một phản hồi tiêu thụ trống và Apple sẽ hiểu đó là mức sử dụng tối thiểu, điều này nghiêng cán cân quyết định về phía chấp thuận, kể cả với những người dùng sử dụng nhiều. Thay vào đó, nếu bịa ra một con số ước tính, bạn đang báo cáo thông tin không đúng sự thật cho một bên nắm giữ nền tảng. Dù bạn chọn cách nào, những giao dịch bán hàng mà bạn có thể đã chứng minh được vẫn bị hoàn tiền, sau khi bạn đã trả tiền để thực hiện chúng.
Chi phí thực hiện là phần gây đau nhất, vì nó không liên quan gì đến giá bán. Các lệnh gọi API mô hình được tiêu thụ trong một lần tạo sinh, hình ảnh được tạo ra, một video được dựng và xuất, một khoản thanh toán cho người sáng tạo được giải ngân ở phía sau: tất cả số tiền đó đã rời khỏi tài khoản của bạn tại thời điểm giao hàng, và việc hoàn tiền cho khách hàng không làm gì để lấy lại số tiền đó. Giờ hãy nhân rộng vấn đề này lên. Một khách hàng ẩn danh liên tục gửi yêu cầu. Những người có thói quen xin hoàn tiền thường nhắm cụ thể vào các doanh nghiệp không thể xác định được họ. Lúc đó, token bị bỏ qua không còn là một sơ suất nhỏ nữa, mà bắt đầu hoạt động như một vòi nước bị mở toang.
Câu hỏi của Apple liên quan đến mức sử dụng thực tế của một khách hàng, và để trả lời cần phải biết khách hàng đó là ai. Không có token, bạn sẽ mất đi những giao dịch bán hàng mà bạn có thể đã chứng minh được, cùng với chi phí tính toán (compute) và chi phí API đã bỏ ra để cung cấp chúng.
Android thực hiện chiến thuật tương tự dưới một cái tên khác
setObfuscatedAccountId thực hiện công việc này trên Google Play, gắn một mã định danh tài khoản vào một giao dịch mua để việc xem xét tranh chấp thanh toán (chargeback review) thông qua orders.reviewrefund có một người thực sự để xem xét. Cách triển khai khác nhau đáng kể, nhưng nguyên tắc cốt lõi vẫn được giữ nguyên không đổi: danh tính được gắn kết trong lúc giao dịch, hoặc tranh chấp sẽ mặc nhiên bị thua sau đó. Phiên bản của Apple cho nguyên tắc đó là appAccountToken, và nó yêu cầu bắt buộc phải là một UUID.
Ba cách làm giúp mọi giao dịch mua luôn được xác định danh tính
Tạo một UUID cho mỗi tài khoản và lưu lại nó. Một token duy nhất, bền vững cho mỗi khách hàng, được tạo khi đăng ký hoặc lần thanh toán đầu tiên, ghi vào hồ sơ của họ, và sử dụng lại cho mọi lần họ mua hàng.
Kiểm tra giá trị trước khi gửi đi. Xác minh rằng đó thực sự là một UUID ngay trong đoạn mã mua hàng của bạn, để một mã định danh sai định dạng không bao giờ có thể âm thầm trở thành một token nil trên giao dịch.
Bổ sung ngược (backfill) cho bất cứ thứ gì được mua bên ngoài ứng dụng. Khi một thông báo máy chủ đến cho một lượt đổi mã ưu đãi hoặc một giao dịch mua được quảng bá mà không mang token, hãy dùng điểm cuối Set App Account Token và gắn token đúng vào.
Áp dụng cả ba cách này và việc xác định danh tính không còn là điều bạn phải cố gắng thực hiện sau khi sự việc đã xảy ra. Nó trở thành một thuộc tính mà mọi giao dịch đã sẵn có khi Apple chuyển nó cho bạn, bao gồm cả các yêu cầu hoàn tiền.
Refund Sensor được xây dựng trực tiếp dựa trên giả định đó. Chúng tôi trích xuất appAccountToken từ mỗi giao dịch và thông báo máy chủ, đối chiếu nó với dữ liệu sử dụng đã được ghi nhận sẵn cho tài khoản đó, và gửi một phản hồi cho yêu cầu của Apple trong khung thời gian mười hai giờ bằng các số liệu thực sự mô tả đúng người mua đó. Một khoản hoàn tiền mà bạn có thể gọi tên là một khoản hoàn tiền bạn có thể tranh luận, và đó là loại duy nhất đáng để tranh luận.
Nơi các quy định này được ghi lại
Câu hỏi thường gặp
Đó là một UUID do hệ thống của bạn sở hữu, còn StoreKit chỉ đơn thuần ghi lại nó. Bạn cung cấp nó trong lúc thanh toán, Apple ghi nó vào giao dịch được tạo ra, và từ đó nó xuất hiện lại ở mọi nơi giao dịch đó xuất hiện: trong thông tin giao dịch, trong mọi thông báo máy chủ liên quan đến nó, và trong từng lượt gia hạn tiếp theo trong cùng chuỗi đó. Hiệu quả thực tế là bất cứ điều gì Apple thông báo cho bạn sau này, bao gồm cả yêu cầu hoàn tiền, đều có thể được đối chiếu với đúng dòng dữ liệu trong cơ sở dữ liệu của chính bạn.
Gần như chắc chắn là do định dạng. RFC 4122 là chuẩn mà StoreKit 2 chấp nhận, và một giá trị không đạt chuẩn này sẽ bị loại bỏ mà không phát sinh lỗi nào, để lại nil trong khi giao dịch mua vẫn hoàn tất như thể không có gì sai. Các id bản ghi nội bộ, địa chỉ email, và các chuỗi ghép lại từ nhiều phần đều thất bại theo cách này. Khả năng khác là ứng dụng của bạn chưa bao giờ xử lý giao dịch mua đó, với việc đổi mã ưu đãi (offer code) thường là nguyên nhân phổ biến, nên không có đoạn mã nào của bạn chạy để cung cấp token.
Từ năm 2025 thì có. Apple đã bổ sung Set App Account Token vào App Store Server API chính là cho tình huống này. Một yêu cầu PUT tới /inApps/v1/transactions/{originalTransactionId}/appAccountToken, với UUID được cung cấp trong phần body, sẽ ghi một token vào một giao dịch đã tồn tại, hoặc thay thế token đang có ở đó. Mọi loại sản phẩm đều đủ điều kiện, và tính năng này được thiết kế dành cho các giao dịch mua mà ứng dụng của bạn chưa bao giờ xử lý, đặc biệt là mã ưu đãi và giao dịch mua được quảng bá (promoted purchases).
Có, không có ngoại lệ. Việc UUID đó trỏ đến điều gì ở phía bạn hoàn toàn là việc riêng của bạn và Apple không bao giờ kiểm tra điều đó, nhưng giá trị bản thân nó phải đáp ứng định dạng UUID. Nếu không đạt, StoreKit sẽ từ chối giữ lại nó, để lại nil trên giao dịch.
Hoàn tiền sẽ kích hoạt một CONSUMPTION_REQUEST, và Apple mong đợi một phản hồi trong vòng mười hai giờ mô tả cách người mua cụ thể đó đã sử dụng sản phẩm. Để tạo ra phản hồi như vậy, bạn cần chuyển đổi giao dịch trong thông báo thành một bản ghi khách hàng mà bạn có thể thực sự truy vấn, và đó chính xác là chức năng của token. Nó cũng xuất hiện như một trường trong yêu cầu đó. Nếu không có nó, phản hồi của bạn sẽ hoặc để trống hoặc bị bịa đặt, và một phản hồi để trống thường dẫn đến việc hoàn tiền được chấp thuận.



