-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: build module for validating receipts
- Loading branch information
1 parent
f3590e4
commit 791fae3
Showing
82 changed files
with
11,076 additions
and
4,952 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
--- | ||
name: 🐛 Bug Report | ||
about: If something isn't working as expected | ||
--- | ||
|
||
<!-- Thanks for using this project! Before you submit your issue, please make sure you followed our checklist and check the appropriate boxes by putting an x in the [ ]: [x] --> | ||
|
||
### New Issue Checklist | ||
|
||
- [ ] What version are you using? (Run `npm ls dollabill-apple` to get it): | ||
- [ ] I read [the documentation](https://github.com/levibostian/dollabill-apple/) | ||
- [ ] I searched for [existing GitHub issues](https://github.com/levibostian/dollabill-apple/issues) | ||
|
||
### Issue Description | ||
|
||
<!-- Please include what's happening, expected behavior, and any relevant code samples. The more you include, the better help you will get back. --> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
--- | ||
name: 💡 Feature request | ||
about: If you have an idea or something to improve | ||
--- | ||
|
||
<!-- Thanks for using this project! Before you submit your issue, please make sure you followed our checklist and check the appropriate boxes by putting an x in the [ ]: [x] --> | ||
|
||
### New Issue Checklist | ||
|
||
- [ ] What version are you using? (Run `npm ls dollabill-apple` to get it): | ||
- [ ] I read [the documentation](https://github.com/levibostian/dollabill-apple/) to see if the project can already do what I am suggesting. | ||
- [ ] I searched for [existing GitHub issues](https://github.com/levibostian/dollabill-apple/issues) to see if anyone else had the same idea. | ||
|
||
### What is the feature request you had in mind? | ||
|
||
<!-- Please include what you are using this project to do. Explain your use cases and the problems you are facing. The more you include, the better help you will get back. --> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
--- | ||
name: 😄 Help | ||
about: You have a question about the project | ||
--- | ||
|
||
<!-- Thanks for using this project! Before you submit your issue, please make sure you followed our checklist and check the appropriate boxes by putting an x in the [ ]: [x] --> | ||
|
||
### New Issue Checklist | ||
|
||
- [ ] What version are you using? (Run `npm ls dollabill-apple` to get it): | ||
- [ ] I read [the documentation](https://github.com/levibostian/dollabill-apple/) to see if it could answer my question. | ||
- [ ] I searched for [existing GitHub issues](https://github.com/levibostian/dollabill-apple/issues) to see if anyone else has asked the same question. | ||
|
||
### What's your question 😄 | ||
|
||
<!-- Please include what's happening, expected behavior, and any relevant code samples. The more you include, the better help you will get back. --> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
blank_issues_enabled: false |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,7 @@ | ||
coverage/ | ||
dist/ | ||
secrets.json | ||
junit.xml | ||
|
||
# Logs | ||
logs | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
declare type Options = { | ||
url: string | ||
data?: any // form vars for tiny.post, tiny.put, tiny.patch, and tiny.delete otherwise querystring vars for tiny.get | ||
headers?: any // key/value map used for headers (including support for uploading files with multipart/form-data) | ||
buffer?: boolean // if set to true the response body is returned as a buffer | ||
timeout?: number | ||
} | ||
|
||
declare type Callback = (err?: Error, res: { headers: any; body: any }) => void | ||
|
||
export declare function post(options: Options): Promise<any> | ||
export declare function post(options: Options, callback: Callback): void | ||
|
||
export declare function get(options: Options): Promise<any> | ||
export declare function get(options: Options, callback: Callback): void |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,112 @@ | ||
# npm-module-blanky | ||
# dolla bill | ||
|
||
Opinionated boilerplate used to make and deploy npm modules. | ||
Easily work with Apple in-app purchases. So you can more easily collect your "Dolla dolla bills, ya'll" | ||
|
||
# Goals of this project | ||
# Why use dolla bill? | ||
|
||
* Contain configuration files to setup all tools I tend to use in my development flow. | ||
* Clone, rename some files, and get developing! | ||
* Start with zero dependencies. I try my best to keep all npm modules as slim as possible. | ||
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. | ||
|
||
- [x] **No dependencies**. This module tries to be as small as possible. | ||
- [x] **Fully tested**. Automated testing (with high test coverage) + QA testing gives you the confidence that the module will work well for you. | ||
- [x] **Complete API documentation** to more easily use it in your code. | ||
- [x] **Up-to-date** If and when Apple changes the way you perform verification or Apple adds more features to verification, update this module and your projects can take advantage of the changes quickly and easily. | ||
- [x] **Typescript typings** This project is written in Typescript for that strict typing goodness. | ||
|
||
> Note: Technically there is [one dependency](https://github.com/brianleroux/tiny-json-http) but it's a _tiny wrapper around a built-in nodejs feature_. This module still remains super small with this. | ||
# Getting started | ||
|
||
```ts | ||
import dollaBill from "dollabill" | ||
|
||
const receiptFromStoreKit = // The base64 encoded receipt data that StoreKit gave your app | ||
|
||
const appleResponse = await dollaBill.verifyReceipt({ | ||
appPassword: process.env.APPLE_IN_APP_PURCHASE_PASSWORD, | ||
receipt: receiptFromStoreKit | ||
}) | ||
|
||
// If an error happens, it's fatal. It means that there is a bug with this library (create a GitHub issue with stacktrace and other info, please) or you the developer made a mistake when using this library. | ||
|
||
// It's recommended that you, the developer, views the error message and stacktrace to fix the issue. Note: The error here is not meant to be shown to your users. | ||
|
||
if (!appleResponse.isValid) { | ||
// There was a problem. The request was valid and can be retried. | ||
|
||
// look at the error and act on it however you wish. | ||
// It's recommended that you log the error and then return back a message to your users saying there was a problem. The error here is meant for the developer to see, not a good error to return back to the user. | ||
appleResponse.error | ||
} else { | ||
// 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. | ||
} | ||
``` | ||
|
||
```ts | ||
type Response = AppleResponse | AppleError | ||
|
||
enum AppleErrorType {} | ||
// TODO | ||
|
||
type AppleError = { | ||
code: number | ||
type: AppleErrorType | ||
} | ||
|
||
type AppleResponse = { | ||
isValid: boolean | ||
autoRenewableSubscription?: AutoRenewableSubscription // If your in-app purchase is for auto renewable subscriptions, go here. | ||
decodedReceipt: Receipt | ||
raw: any // just in case you need it, here is the raw JSON response from Apple. https://developer.apple.com/documentation/appstorereceipts/responsebody | ||
} | ||
|
||
type AutoRenewableSubscription = { | ||
transactions: Array<Transaction> | ||
pendingRenewals: Array<PendingRenewal> | ||
isGracePeriod: bool | ||
gracePeriod: GracePeriodInfo | ||
} | ||
|
||
type GracePeriodInfo = { | ||
// TODO | ||
// make this up. it comes from: https://developer.apple.com/documentation/appstorereceipts/responsebody/pending_renewal_info | ||
} | ||
|
||
type PendingRenewal = { | ||
// TODO | ||
// https://developer.apple.com/documentation/appstorereceipts/responsebody/pending_renewal_info | ||
} | ||
|
||
type Transaction = { | ||
// TODO | ||
// https://developer.apple.com/documentation/appstorereceipts/responsebody/latest_receipt_info | ||
} | ||
|
||
type Receipt = { | ||
// TODO | ||
// https://developer.apple.com/documentation/appstorereceipts/responsebody/receipt | ||
} | ||
``` | ||
# Documentation | ||
// TODO | ||
## Contributors | ||
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)) | ||
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section --> | ||
<!-- prettier-ignore-start --> | ||
<!-- markdownlint-disable --> | ||
<table> | ||
<tr> | ||
<td align="center"><a href="https://github.com/levibostian"><img src="https://avatars1.githubusercontent.com/u/2041082?v=4" width="100px;" alt=""/><br /><sub><b>Levi Bostian</b></sub></a><br /><a href="https://github.com/levibostian/Purslane/commits?author=levibostian" title="Code">💻</a> <a href="https://github.com/levibostian/Purslane/commits?author=levibostian" title="Documentation">📖</a> <a href="#maintenance-levibostian" title="Maintenance">🚧</a></td> | ||
</tr> | ||
</table> | ||
<!-- markdownlint-enable --> | ||
<!-- prettier-ignore-end --> | ||
<!-- ALL-CONTRIBUTORS-LIST:END --> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
/** | ||
* @internal | ||
*/ | ||
export const bugReportLink = `https://github.com/levibostian/dollabill-apple/issues/new?template=BUG_REPORT.md` | ||
/** | ||
* @internal | ||
*/ | ||
export const appleProductionVerifyReceiptEndpoint = "https://buy.itunes.apple.com/verifyReceipt" | ||
/** | ||
* @internal | ||
*/ | ||
export const appleSandboxVerifyReceiptEndpoint = "https://sandbox.itunes.apple.com/verifyReceipt" |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
import _ from "./underscore" | ||
|
||
describe("array", () => { | ||
describe("contains", () => { | ||
it(`given empty array, expect false`, async () => { | ||
const given: string[] = [] | ||
|
||
const actual = _.array.contains(given, (item) => item === "foo") | ||
|
||
expect(actual).toEqual(false) | ||
}) | ||
it(`given array without item, expect false`, async () => { | ||
const given: string[] = ["bar"] | ||
|
||
const actual = _.array.contains(given, (item) => item === "foo") | ||
|
||
expect(actual).toEqual(false) | ||
}) | ||
it(`given array with item, expect true`, async () => { | ||
const given: string[] = ["bar", "foo"] | ||
|
||
const actual = _.array.contains(given, (item) => item === "foo") | ||
|
||
expect(actual).toEqual(true) | ||
}) | ||
}) | ||
}) | ||
|
||
describe("date", () => { | ||
describe("sortOldToNew", () => { | ||
it(`given empty array, expect empty array`, async () => { | ||
const given: Date[] = [] | ||
|
||
given.sort(_.date.sortOldToNew) | ||
|
||
expect(given).toEqual([]) | ||
}) | ||
it(`given items already in order, expect unchanged`, async () => { | ||
const given: Date[] = [new Date('2018-04-04T16:00:00.000Z'), new Date('2020-04-04T16:00:00.000Z')] | ||
const expected = given | ||
|
||
given.sort(_.date.sortOldToNew) | ||
|
||
expect(given).toEqual(expected) | ||
}) | ||
it(`given items not in order, expect to sort`, async () => { | ||
const given: Date[] = [new Date('2020-04-04T16:00:00.000Z'), new Date('2018-04-04T16:00:00.000Z')] | ||
const expected = [given[1], given[0]] | ||
|
||
given.sort(_.date.sortOldToNew) | ||
|
||
expect(given).toEqual(expected) | ||
}) | ||
it(`given identical dates, expect unchanged`, async () => { | ||
const given: Date[] = [new Date('2018-04-04T16:00:00.000Z'), new Date('2018-04-04T16:00:00.000Z')] | ||
const expected = given | ||
|
||
given.sort(_.date.sortOldToNew) | ||
|
||
expect(given).toEqual(expected) | ||
}) | ||
}) | ||
describe("sortNewToOld", () => { | ||
it(`given empty array, expect empty array`, async () => { | ||
const given: Date[] = [] | ||
|
||
given.sort(_.date.sortNewToOld) | ||
|
||
expect(given).toEqual([]) | ||
}) | ||
it(`given items already in order, expect unchanged`, async () => { | ||
const given: Date[] = [new Date('2020-04-04T16:00:00.000Z'), new Date('2018-04-04T16:00:00.000Z')] | ||
const expected = given | ||
|
||
given.sort(_.date.sortNewToOld) | ||
|
||
expect(given).toEqual(expected) | ||
}) | ||
it(`given items not in order, expect to sort`, async () => { | ||
const given: Date[] = [new Date('2018-04-04T16:00:00.000Z'), new Date('2020-04-04T16:00:00.000Z')] | ||
const expected = [given[1], given[0]] | ||
|
||
given.sort(_.date.sortNewToOld) | ||
|
||
expect(given).toEqual(expected) | ||
}) | ||
it(`given identical dates, expect unchanged`, async () => { | ||
const given: Date[] = [new Date('2018-04-04T16:00:00.000Z'), new Date('2018-04-04T16:00:00.000Z')] | ||
const expected = given | ||
|
||
given.sort(_.date.sortNewToOld) | ||
|
||
expect(given).toEqual(expected) | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
const _ = { | ||
array: { | ||
contains<T>(array: T[], doesContain: (element: T) => boolean): boolean { | ||
let result = false | ||
|
||
array.forEach((element) => { | ||
if (doesContain(element)) { | ||
result = true | ||
} | ||
}) | ||
|
||
return result | ||
} | ||
}, | ||
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)) | ||
* ``` | ||
*/ | ||
sortOldToNew(first: Date, second: Date): number { | ||
return first.getTime() - second.getTime() | ||
}, | ||
/** | ||
* 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)) | ||
* ``` | ||
*/ | ||
sortNewToOld(first: Date, second: Date): number { | ||
return second.getTime() - first.getTime() | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* @internal | ||
*/ | ||
export default _ |
Oops, something went wrong.