{"id":23143098,"url":"https://github.com/cleancocoa/fastspringstore","last_synced_at":"2025-06-25T04:34:41.079Z","repository":{"id":220309178,"uuid":"751298552","full_name":"CleanCocoa/FastSpringStore","owner":"CleanCocoa","description":"macOS component for in-app purchases using a FastSpring store","archived":false,"fork":false,"pushed_at":"2024-05-10T07:30:12.000Z","size":253,"stargazers_count":10,"open_issues_count":1,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-06-05T14:16:21.417Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/CleanCocoa.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2024-02-01T10:30:01.000Z","updated_at":"2024-07-02T16:00:38.000Z","dependencies_parsed_at":"2024-02-10T18:46:09.208Z","dependency_job_id":null,"html_url":"https://github.com/CleanCocoa/FastSpringStore","commit_stats":null,"previous_names":["cleancocoa/fastspringstore"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/CleanCocoa/FastSpringStore","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CleanCocoa%2FFastSpringStore","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CleanCocoa%2FFastSpringStore/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CleanCocoa%2FFastSpringStore/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CleanCocoa%2FFastSpringStore/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/CleanCocoa","download_url":"https://codeload.github.com/CleanCocoa/FastSpringStore/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CleanCocoa%2FFastSpringStore/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":261806659,"owners_count":23212595,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-12-17T15:12:09.623Z","updated_at":"2025-06-25T04:34:41.029Z","avatar_url":"https://github.com/CleanCocoa.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# FastSpringStore\n\nInterfaces with FastSpring Embedded Storefrontsto offer secure in-app purchase windows.\n\n\u003e [!TIP]\n\u003e For setup instructions of your own store and app preparation with FastSpring, take a look at my book [_Make Money Outside the Mac App Store_](https://christiantietze.de/books/make-money-outside-mac-app-store-fastspring/).\n\n**Works exceptionally well with my [Trial \u0026 Licensing package](https://github.com/CleanCocoa/TrialLicensing)** that's based on CocoaFob for license code generation.\n\n[Embedded Storefront]: https://developer.fastspring.com/docs/building-and-adding-an-embedded-storefront-to-your-website\n\n## Installation\n\n### Swift Package Manager\n\n```swift\ndependencies: [\n  .package(url: \"https://github.com/CleanCocoa/FastSpringStore.git\", .upToNextMajor(from: Version(1, 0, 0))),\n]\n```\n\nAdd package depencency via Xcode by using this URL: `https://github.com/CleanCocoa/FastSpringStore.git`\n\n## Usage\n\nThe process requires two pieces:\n\n1. Online store, using FastSpring's [Embedded Storefront][], and\n2. This package to display the store and tell your app about purchases.\n\n### Embedded Storefront Setup\n\n**If you already have an Embedded Storefront, you can skip this step.**\n\nEmbedded Storefronts are a minimal version of the store UI. They only show the checkout details, nothing more.\n\nTo setup the store:\n\n1. Log into the [FastSpring Dashboard](https://app.fastspring.com/),\n2. navigate to _Storefronts ▶ Embedded Storefronts_,\n3. \"Create Embedded Storefront\" and pick a unique ID for the storefront.\n\n![Screenshot of the Embedded Storefront called \"test\"](./assets/screenshot-embedded-store_0_new.png)\n\nNote that the new storefront starts with:\n\n- \"Offline\" mode, so it's only available for testing;\n- \"No whitelisted websites\", so you can't embed it anywhere, yet;\n- No products in the storefront, so you can't offer anything, yet.\n\nI suggest starting with \"Products\": add the app you want to sell to the store.\n\nThen \"whitelist\" your website so you can put the Embedded Store online in the next step.\n\n#### Storefront HTML\n\nYou can start with the template from the \"Place On Your Website\" action of your Embedded Store, then\n\n- set the `data-popup-webhook-received=\"fsprg_dataPopupWebhookReceived\"` attribute to the script, and\n- add the product to the store's cart.\n\nThis is the result, with placeholders in all caps:\n\n```html\n\u003cscript\n  id=\"fsc-api\"\n  src=\"https://sbl.onfastspring.com/sbl/0.9.6/fastspring-builder.min.js\"\n  type=\"text/javascript\"\n  data-storefront=\"STOREFRONT-ID\"\n  data-popup-webhook-received=\"fsprg_dataPopupWebhookReceived\"\u003e\u003c/script\u003e\n\u003cdiv id=\"fsc-embedded-checkout-container\"\u003e\u003c/div\u003e\n\u003cscript\u003efastspring.builder.add(\"PRODUCT-ID\");\u003c/script\u003e\n```\n\nThat's all the HTML you really need.\n\nReplace the placeholders `PRODUCT-ID` and `STOREFRONT-ID`, then upload this to your whitelisted domain.\n\nIn \"Offline\" mode, the `STOREFRONT-ID` will include \"`test.onfastspring`\" in the subdomain part of the URL (e.g. `yourstore.test.onfastspring.com/embedded-test`). You can't access the regular one while in \"Offline\" mode, but after you switch the Embedded Store to \"Online\", you can use both. We'll make use of that for debugging.\n\nWhen the storefront is displayed in your app, [this package will inject][inject] the callback `fsprg_dataPopupWebhookReceived` into the web view to handle purchases. You don't need to implement any JavaScript here.\n\n\u003e [!TIP]\n\u003e I suggest uploading two versions of this template:\n\u003e\n\u003e 1. The live store used in production, e.g. at `myapp.com/embedded-store`\n\u003e 2. The test store used internally for `DEBUG` builds, e.g. at `myapp.com/embedded-store/test`.\n\u003e\n\u003e This way, you can test the checkout process with FastSpring's placeholder credit cards details. The test store includes a prominent badge at the top. Clicking it reveals how you can place test purchases:\n\u003e\n\u003e ![\"Test Mode\" badge](assets/screenshot-test-mode.png)\n\n[inject]: https://github.com/CleanCocoa/FastSpringStore/blob/5a70b488a72c623bb85b703c8c3533f67125c57d/Sources/FastSpringStore/FastSpringStoreJavaScriptBridge.swift#L55\n\n#### Verify Your Embedded Store Setup\n\nCheck that the Embedded Store works. Access the HTML file you prepared in your web browser.\n\nIf it works in the browser, it'll work in the app.\n\nSo once you see a checkout form to make purchases, you're good to go. We'll embed that in the app next.\n\n\u003e [!WARNING]\n\u003e \u003cimg alt=\"Screenshot of the store's loading animations\" src=\"assets/screenshot-loading-failure.png\" width=\"40%\"\u003e\n\u003e\n\u003e Do you just see placeholder loading animations?\n\u003e\n\u003e - Make sure you're using the `test` URL while in \"Offline\" mode,\n\u003e - or switch to \"Online\" before accessing the store for production.\n\u003e - Ensure a product is added to the cart, otherwise the store won't load:\n\u003e     ```html\n\u003e     \u003cscript\u003efastspring.builder.add(\"PRODUCT-ID\");\u003c/script\u003e\n\u003e     ```\n\n### Using the Embedded Storefront in Your App\n\nImport this packagage into your project. Then use `FastSpringStore` to access your Embedded Storefront HTML.\n\nThe following code example ties together\n\n- setting up your Embedded Storefront URLs,\n- initializing the `FastSpringStore` UI to display the Embedded Store, and finally\n- unlocks app functionality after a purchase.\n\n```swift\n#if DEBUG\n// Test store that accepts credit card placeholder values:\nprivate let storeURL = URL(string: \"https://myapp.com/embedded-store/test\")!\n#else\nprivate let storeURL = URL(string: \"https://myapp.com/embedded-store\")!\n#endif\n\nclass PurchaseLicense {\n    let store: FastSpringStore\n\n    init() {\n        self.store = FastSpringStore(\n            storeURL: storeURL,\n            purchaseCallback: { store, purchases in\n                // Could have multiple purchased items, e.g. if you\n                // offer in-app purchases of bundled features.\n                assert(purchases.count == 1)\n                guard let purchase = purchases.first else { return }\n\n                for license in purchase.licenses {\n                    // Unlock via license.licenseName \u0026 license.licenseCode, e.g.\n                    // using the https://github.com/CleanCocoa/TrialLicensing package:\n                    //\n                    //   TrialLicensing.AppLicensing.register(\n                    //       name: license.licenseName,\n                    //       licenseCode: license.licenseCode\n                    //   )\n                }\n            })\n    }\n\n    func showStore() {\n        // Localize and customize the window title:\n        store.showStore(title: \"Purchase MyApp\")\n    }\n}\n```\n\nAdd this to your app, e.g. by abusing the popular _Put Everything Into the AppDelegate_ Anti-Pattern:\n\n```swift\n// AppDelegate.swift\n@NSApplicationMain\nclass AppDelegate: NSObject, NSApplicationDelegate {\n    // ...\n\n    lazy var purchaseLicense = PurchaseLicense()\n\n    @IBAction func buyApp(_ sender: Any?) {\n        purchaseLicense.showStore()\n    }\n\n    // ...\n}\n```\n\nWhen you call `showStore()`, you should see a small window appear that displays your Embedded Storefront similar to how the web browser does.\n\n\u003e [!WARNING]\n\u003e \u003cimg alt=\"Screenshot of the store's loading animations\" src=\"assets/screenshot-loading-failure.png\" width=\"40%\"\u003e\n\u003e\n\u003e If you just see placeholder loading animations, verify that you can access the URL in your web browser, first.\n\u003e\n\u003e Then check that you are using the correct store URL, e.g. the `test.onfastspring` subdomain in `DEBUG` builds, or the live URL for production, and that your Embedded Store is actually \"Online\".\n\n## Preparing to Ship\n\nGo through the checkout flow with the test store to verify that the purchase works and that your app is being unlocked successfully.\n\nThe switch from \"debug\" to \"release\" is to replace the `test.onfastspring` subdomain with `onfastspring` in the Embedded Store HTML. The rest of the setup doesn't change. If you followed my advice, you have two HTML files for this anyway, so you do not need to change the HTML files, only the store URLs.\n\n1. Change the Embedded Store's state in the FastSpring Dashboard from \"Offline\" to \"Online\" if you haven't already.\n2. Verify that the Embedded Store can be accessed from the browser.\n3. Verify that the Embedded Store doesn't show the \"Test Mode\" badge at the top anymore.\n   ![\"Test Mode\" badge](assets/screenshot-test-mode.png)\n4. Double-check that the `data-popup-webhook-received` attribute is set properly. Otherwise, your store will work nicely, but your app won't unlock automatically.\n\nSo when you make a release build, **make sure to test that automatic unlocking still works** at least once. I suggest setting up a 100% discount coupon for this so you don't incur any purchase fees.\n\n## Architecture\n\nThis package doesn't have a lot going on in terms of architecture, but here are the most important parts:\n\n- [`FastSpringStore`](https://github.com/CleanCocoa/FastSpringStore/blob/main/Sources/FastSpringStore/FastSpringStore.swift) is your **entry point**. It manages the store window and forwards purchase callbacks.\n\n    Simplified, the API is:\n    ```swift\n    class FastSpringStore {\n        init(\n            storeURL: URL,\n            purchaseCallback: @escaping (FastSpringStore, [FastSpringPurchase]) -\u003e Void\n        )\n        func showStore(title: String)\n        func closeWindow()\n    }\n    ```\n\n- [`FastSpringPurchase`](https://github.com/CleanCocoa/FastSpringStore/blob/main/Sources/FastSpringStore/FastSpringPurchase.swift) is the payload (_Data-Transfer Object_) that contains all information of a successful purchase. You can offer multiple products in a store, and bundles, too. So you will get an array of purchases and each purchase can contain multiple `License`s.\n\n    Simplified:\n    ```swift\n    struct FastSpringPurchase {\n        struct License: Equatable {\n            enum LicenseType { case cocoaFob }\n\n            let licenseCode: String\n            let licenseName: String\n            let licenseType: LicenseType\n        }\n\n        let product: String\n        let quantity: Int\n        let licenses: [License]\n    }\n    ```\n\n## Privacy Manifest\n\n- The `FastSpringStore` target does not collect or track anything, and your app is responsible for storing any user data of the purchase (see examples above).\n- The `FastSpringClassicStore` target handles the checkout flow internally. It depends on [`TrialLicensing`](https://github.com/CleanCocoa/TrialLicensing) to store the result in `UserDefaults` and declares storage of email and name.\n\n\n## License\n\nCopyright (c) 2015 by [Christian Tietze](https://christiantietze.de/). Distributed under the MIT License. See the LICENSE file for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcleancocoa%2Ffastspringstore","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcleancocoa%2Ffastspringstore","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcleancocoa%2Ffastspringstore/lists"}