Easily work with Apple in-app purchases. So you can more easily collect your "Dolla dolla bills, ya'll".
dolla bill makes it very easy to...
Note: If you have already written the code to verify receipts from Apple, you may be interested in Typescript type definitions for Apple's responses.
When implementing Apple in-app purchase receipt validation, it takes quite a bit of work (especially auto-renewable subscriptions!). There are many steps:
dolla bill takes care of step #2 above. From my experience, step #2 is the most work and biggest pain!
Dolla bill helps you to:
Example: Let's say that you need to determine if a customer is eligible to receive a discount on a new subscription.
Before dolla bill...
const transactions = parseAppleResponse(responseBody).transactions // parse Apple HTTP response to get transactions
const subscriptions = transactions.filter(...) // get only the subscription transactions
const subscriptionGroups = ... // group all of the subscriptions into different groups
const isSubscriptionGroupEligibleForDiscount = subscriptionGroups.transactions.forEach(transaction => transaction.in_trial_period)
Note: The code above is pseudocode. The real code is at least double the amount of work
After dolla bill...
dollaBillResponse.autoRenewableSubscriptions[0].isEligibleIntroductoryOffer
dolla bill parsed the results back from Apple and just hands to you the info that you actually care about.
That's just 1 example. You also need to write code to determine...
When you are accepting in-app purchases in an iOS mobile app, you need to verify the transaction and continuously update your customer's subscription status. This verification and parsing of the responses back from Apple is boilerplate. This small module exists to help you with these tasks.
Note: Technically there is one dependency but it's a tiny wrapper around a built-in nodejs feature. This module still remains super small with this.
import {verifyReceipt, isFailure, AutoRenewableSubscription} from "dollabill-apple"
// Note: The typescript typings for the raw Apple responses are in the npm module: types-apple-iap
// Full API documentation is also available for those typings: https://github.com/levibostian/types-apple-iap/#documentation
import { AppleReceipt } from "types-apple-iap"
const receiptFromStoreKit = // base64 encoded string StoreKit has given you in your app.
// It's optional to wrap verifyReceipt() in a try/catch.
// If an error is thrown, more then likely it's a bug with our module, not your code.
// All errors that *could* happen are caught for you and returned from the Promise.
const appleResponse = await verifyReceipt({
receipt: receiptFromStoreKit,
sharedSecret: process.env.APP_STORE_CONNECT_SHARED_SECRET // https://stackoverflow.com/a/56128978/1486374
})
if (isFailure(appleResponse)) {
// There was a problem. The receipt was not valid, the shared secret you passed in didn't match. The receipt is not valid. Apple's servers were down.
// It's recommended that you, the developer, log the error. We have tried to make the error messages developer friendly to help you fix the problem.
// It's *not* recommended that you return the error to your customer. You should return back your own message as the error here is not human friendly.
} else {
// The receipt has been verified by Apple and parsed by the module! Yay!
// API reference for the result object: https://levibostian.github.io/dollabill-apple/api/interfaces/parsedreceipt.html
// Time for you to update your database with the status of your customer and their subscription.
// This is easy because dolla bill parses the response from Apple to be easily readable.
// Check out the API documentation to learn about what `appleResponse` properties there are.
handleSuccessfulResult(appleResponse)
}
// dollabill produces the same result when verifying receipts *and*
// parsing server-to-server notifications. This means you can re-use
// your app's logic for handling the result of each!
const handleSuccessfulResult = (result: ParsedResult): void => {
const subscriptions: AutoRenewableSubscription[] = result.autoRenewableSubscriptions
// You shouldn't need to, but you can access the raw response from Apple.
// Hopefully, dollabill has parsed enough helpful information for you that you don't need to use this. It's here just in case.
result.rawResponse
console.log(`App where purchase was made: ${result.bundleId}`)
// API for subscription: https://levibostian.github.io/dollabill-apple/api/interfaces/autorenewablesubscription.html
subscriptions.forEach(subscription => {
// It's up to you to write all of the code needed to update your customer's subscription statuses. At this time, Apple recommended you maintain a database stored with:
// 1. The original transaction id for each subscription:
subscription.originalTransactionId
// 2. The current end date that the subscription is valid for. You can use:
subscription.expireDate
// but this does not account for grace periods or refund/cancellations. Dolla bill provides a convenient Date property instead:
subscription.currentEndDate
// 3. Keep track if the customer is eligible for a subscription offer or not.
subscription.isEligibleIntroductoryOffer
// Eligibility for intro offers is grouped by the subscription group, not by the original transaction id. So make sure you keep track of the subscription group, too:
subscription.subscriptionGroup
// 4. Keep the latest receipt in case you need to call `verifyReceipt()` in the future to get a status of the customer's subscription:
result.latestReceipt
// Beyond these basics, there are more advanced things you can do. Some things to help reduce the number of customers leaving your subscription.
subscription.issues.notYetAcceptingPriceIncrease // Customer has been notified about price increase but not accepting it yet.
subscription.issues.willVoluntaryCancel // Customer will not automatically renew
subscription.issues.billingIssue // Customer is having billing issues with renewing.
subscription.status // Gives a status telling you quickly what state the subscription is in.
})
}
Super easy, especially if you have already written your code for verifying receipts.
// Note: The typescript typings for the raw Apple responses are in the npm module: types-apple-iap
// Full API documentation is also available for those typings: https://github.com/levibostian/types-apple-iap/#documentation
import { AppleServerNotificationResponseBody } from "types-apple-iap"
const notification: AppleServerNotificationResponseBody = // server notification sent to you by Apple
const parsedResult = parseServerToServerNotification({
sharedSecret: "foo",
responseBody: notification
})
if (isFailure(parsedResult)) {
// There was a problem. More then likely the shared secret you provided does not match the one in the notification. This means you, the developer, made a typo or there is a malicious person trying to send you fake notifications.
console.error(parsedResult)
} else {
// The notification has been parsed successfully!
// The result type is the same as the result type of `verifyReceipt()` which means that you can
// re-use the same logic in your app to update the customer's subscription status!
handleSuccessfulResult(parsedResult)
}
Full API documentation is hosted here. The documentation, as of now, is a little busy because it includes all code including internal code not meant for you to use.
Convenient quick links:
verifyReceipt()
and parseServerToServerNotification()
The example project is setup to be debuggable.
First, run npm run build
to build the module. The example requires it. Then, you can go into the debugger view in VSCode and run "Debug example" task. It will trigger breakpoints in the module code or the example code.
npm run test
To create a Receipt, follow these steps:
Find this code in the app:
func paymentQueue(_ queue: SKPaymentQueue,updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
switch transaction.transactionState {
case .purchased: // <-------- This line is what we are looking for
}
}
//Handle transaction states here.
}
Add this code:
case .purchased: // <----------- This case is what we are adding to.
if let appStoreReceiptURL = Bundle.main.appStoreReceiptURL,
FileManager.default.fileExists(atPath: appStoreReceiptURL.path) {
do {
let rawReceiptData = try Data(contentsOf: appStoreReceiptURL)
let receipt = rawReceiptData.base64EncodedString(options: [])
print("Receipt: \(receipt)")
queue.finishTransaction(transaction)
}
catch { print("Couldn't read receipt data with error: " + error.localizedDescription) }
}
Run the example app on your computer in simulator or device as a development build. Make a purchase. You will see in the XCode console a line Receipt: XXXXXXX
. Copy the very long string that is printed. That's your receipt.
Run: cp example/secrets.json.example example/secrets.json
and then go into example/secrets.json
and edit it to your receipt and shared secret you just got.
Time to run the QA test! Pretty easy.
npm run qa:setup # You only need to run this once for setup.
npm run qa # Run the actual test
You should expect the script to output to the console JSON of the parsed result.
Thanks goes to these wonderful people (emoji key)
Levi Bostian 💻 📖 🚧 |
Options:
Tip: You can open the link
https://apps.apple.com/account/billing
to send the user to their payment details page in the App Store to update their payment information.
Receipt for an auto-renewable subscription.
The response type of verifyReceipt. A failure or a success.
Make sure to use isFailure after you get this response.
Parses the HTTP response and returns back an Error meant for a developer's purpose. These errors below are meant to be more friendly to help the developer fix the issue.
Determine if a {@link VerifyReceiptResponse} was a failed response or a success.
Here, we are parsing the latest receipt info and in-app purchase transactions.
This is because (1) I am still confused by Apple's documentation on what one to use, when.
(2) the latest receipt info contains the latest transactions which is great, but the
latest receipt info is only available when the receipt contains auto-renewable subscriptions.
(3) This document: https://developer.apple.com/documentation/appstoreservernotifications/unified_receipt
says that latest_receipt_info
only contains the latest 100 receipt transactions. So, I also want to process
the in_app transactions, too.
If you want to perform the HTTP request yourself and just parse the verified receipt, use this function.
If you want to perform the HTTP request yourself and just parse the verified receipt, use this function.
Warning: This function does not have error handling involved when you pass in an object that is not from Apple. It's assumed you will only ever pass in an object from Apple.
Verify an in-app purchase receipt. This is a major feature of this library.
This function will send the receipt to Apple's server and parse the result into something that is much more useful for you, the developer. This allow you to implement in-app purchases faster in your app with server-side verification.
Sort date array in order: [newer date, older date].
Use:
const dates: Date[] = []
dates.sort(_.date.sortNewToOld)
const nestedDates: {date: Date}[] = []
nestedDates.sort((first, second) => _.date.sortNewToOld(first.date, second.date))
Sort date array in order: [older date, newer date].
Use:
const dates: Date[] = []
dates.sort(_.date.sortOldToNew)
const nestedDates: {date: Date}[] = []
nestedDates.sort((first, second) => _.date.sortOldToNew(first.date, second.date))
Generated using TypeDoc
See {@link AutoRenewableSubscriptionActionableStatus}