Ruby Gem for Rails - Easy iTunes In-App Purchase Receipt validation, including auto-renewable subscriptions

Related tags

Payments monza
Overview

Build Status

monza_asset

Monza is a ruby gem that makes In-App Purchase receipt and Auto-Renewable subscription validation easy.

You should always validate receipts on the server, in Apple's words:

Use a trusted server to communicate with the App Store. Using your own server lets you design your app to recognize and trust only your server, and lets you ensure that your server connects with the App Store server. It is not possible to build a trusted connection between a user’s device and the App Store directly because you don’t control either end of that connection.

Installation

Add this line to your application's Gemfile:

gem 'monza'

And then execute:

$ bundle install

Or install it yourself as:

$ gem install monza

Usage

Basic Usage:
data = "base64 receipt data string"
options = { shared_secret: "your shared secret" }
response = Monza::Receipt.verify(data, options)

You can also pass in exclude_old_transactions with value true as an option in the options hash for iOS7 style app receipts.

Useful Methods
# Check if subscription is active
# this checks if latest transaction receipt expiry_date is in today or the future
response.is_subscription_active? # => true or false

# Returns the active subscription TransactionReceipt or nil
response.latest_active_transaction_receipt # => TransactionReceipt instance

# Check most recent expiry date
# ActiveSupport::TimeWithZone
response.latest_expiry_date # => Fri, 17 Jun 2016 01:57:28 UTC +00:00
Response Objects
# Receipt object
# See Receipt class or sample JSON below for full attributes
response.receipt

# Receipt In App Transactions
# Returns array of TransactionReceipt objects, see TransactionReceipt class or sample JSON below for full attributes
response.receipt.in_app

# Receipt Latest Transactions List, use these instead if in_app to ensure you always have the latest
# Returns array of TransactionReceipt objects, see TransactionReceipt class
response.latest_receipt_info # => Array of TransactionReceipt objects

# Expires date of a transaction
# DateTime
response.latest_receipt_info.last.expires_date => # Fri, 17 Jun 2016 01:57:28 +0000

# Check if latest transaction was trial period
response.latest_receipt_info.last.is_trial_period # => true or false

# Latest receipt base64 string
response.latest_receipt

# original JSON response
response.original_json_response
Sample JSON Response Schema
{
  "status": 0,
  "environment": "Sandbox",
  "receipt": {
    "receipt_type": "ProductionSandbox",
    "adam_id": 0,
    "app_item_id": 0,
    "bundle_id": "your_product_id",
    "application_version": "58",
    "download_id": 0,
    "version_external_identifier": 0,
    "receipt_creation_date": "2016-06-17 01:54:26 Etc/GMT",
    "receipt_creation_date_ms": "1466128466000",
    "receipt_creation_date_pst": "2016-06-16 18:54:26 America/Los_Angeles",
    "request_date": "2016-06-17 17:34:41 Etc/GMT",
    "request_date_ms": "1466184881174",
    "request_date_pst": "2016-06-17 10:34:41 America/Los_Angeles",
    "original_purchase_date": "2013-08-01 07:00:00 Etc/GMT",
    "original_purchase_date_ms": "1375340400000",
    "original_purchase_date_pst": "2013-08-01 00:00:00 America/Los_Angeles",
    "original_application_version": "1.0",
    "in_app": [
      {
        "quantity": "1",
        "product_id": "product_id",
        "transaction_id": "1000000218147651",
        "original_transaction_id": "1000000218147500",
        "purchase_date": "2016-06-17 01:32:28 Etc/GMT",
        "purchase_date_ms": "1466127148000",
        "purchase_date_pst": "2016-06-16 18:32:28 America/Los_Angeles",
        "original_purchase_date": "2016-06-17 01:30:33 Etc/GMT",
        "original_purchase_date_ms": "1466127033000",
        "original_purchase_date_pst": "2016-06-16 18:30:33 America/Los_Angeles",
        "expires_date": "2016-06-17 01:37:28 Etc/GMT",
        "expires_date_ms": "1466127448000",
        "expires_date_pst": "2016-06-16 18:37:28 America/Los_Angeles",
        "web_order_line_item_id": "1000000032727764",
        "is_trial_period": "false"
      }
    ]
  },
  "latest_receipt_info": [
    {
      "quantity": "1",
      "product_id": "product_id",
      "transaction_id": "1000000218147500",
      "original_transaction_id": "1000000218147500",
      "purchase_date": "2016-06-17 01:27:28 Etc/GMT",
      "purchase_date_ms": "1466126848000",
      "purchase_date_pst": "2016-06-16 18:27:28 America/Los_Angeles",
      "original_purchase_date": "2016-06-17 01:27:28 Etc/GMT",
      "original_purchase_date_ms": "1466126848000",
      "original_purchase_date_pst": "2016-06-16 18:27:28 America/Los_Angeles",
      "expires_date": "2016-06-17 01:32:28 Etc/GMT",
      "expires_date_ms": "1466127148000",
      "expires_date_pst": "2016-06-16 18:32:28 America/Los_Angeles",
      "web_order_line_item_id": "1000000032727765",
      "is_trial_period": "true"
    }
  ],
  "latest_receipt": "base 64 string"
}
TransactionReceipt Object

An array TransactionReceipt objects will come inside the receipt.in_app and latest_receipt_info keys of the response

{
  "quantity": "1",
  "product_id": "product_id",
  "transaction_id": "1000000218147500",
  "original_transaction_id": "1000000218147500",
  "purchase_date": "2016-06-17 01:27:28 Etc/GMT",
  "purchase_date_ms": "1466126848000",
  "purchase_date_pst": "2016-06-16 18:27:28 America/Los_Angeles",
  "original_purchase_date": "2016-06-17 01:27:28 Etc/GMT",
  "original_purchase_date_ms": "1466126848000",
  "original_purchase_date_pst": "2016-06-16 18:27:28 America/Los_Angeles",
  "expires_date": "2016-06-17 01:32:28 Etc/GMT",
  "expires_date_ms": "1466127148000",
  "expires_date_pst": "2016-06-16 18:32:28 America/Los_Angeles",
  "web_order_line_item_id": "1000000032727765",
  "is_trial_period": "true"
}

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/gabrielgarza/monza. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.

License

The gem is available as open source under the terms of the MIT License.

Comments
  • Fix bug with incorrect expiration dates

    Fix bug with incorrect expiration dates

    It looks like Apple changed the way they send receipts such that they no longer appear in order of expiration date.

    This fixes the issue by removing assumptions about the last receipt from Apple being the active one. Instead we look for the receipt with the most recent expiration date.

    I also added Byebug and Timecop for testing purposes. Timecop I needed to make the tests pass, byebug I could remove if folks don't like it.

    opened by samanthamjohn 13
  • Handling cancellation_date

    Handling cancellation_date

    Thanks for making this awesome gem!

    Question: does the gem handle cancellation_date from the apple response? https://developer.apple.com/library/prerelease/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html#//apple_ref/doc/uid/TP40010573-CH106-SW1

    Seems like response.is_subscription_active? might do it, but I can't find any reference to cancellation_date in the code.

    Thanks again!

    opened by tsabend 8
  • CRITICAL BUG: `latest_expiry_date`, `is_subscription_active?` etc. are wrong

    CRITICAL BUG: `latest_expiry_date`, `is_subscription_active?` etc. are wrong

    Apple does not guarantee an order for the array in @latest_receipt_info. latest_receipt_info.last is not guaranteed to return the newest receipt.

    Monza must explicitly sort @latest_receipt_info by the respective timestamps or things will break (we got bitten by this today 😢).

    opened by m-o-e 6
  • Fix deprecated and move to unified_receipt

    Fix deprecated and move to unified_receipt

    Deprecated: latest_receipt, latest_receipt_info, latest_expired_receipt, latest_expired_receipt_info.

    According Apple documentation latest_receipt, latest_receipt_info, latest_expired_receipt, latest_expired_receipt_info are unavailable (since 10 March 2021) in JSON response.

    This PR will fix that.

    opened by Mihoid 3
  • Verification no longer works if grace period is not available

    Verification no longer works if grace period is not available

    The grace period fields are optional, but commit https://github.com/gabrielgarza/monza/commit/f72358193834d1ae53deada319f634ec1490e369 assumes it will always be present.

    With the grace period off the receipt verification returns nil. The parsing logic needs to confirm the fields are available.

    opened by russellrichardson 3
  • Do not consider a cancelled subscripton active

    Do not consider a cancelled subscripton active

    Apple documentation says the following:

    Cancellation Date

    For a transaction that was canceled by Apple customer support, the time and date of the cancellation. For an auto-renewable subscription plan that was upgraded, the time and date of the upgrade transaction. ... Treat a canceled receipt the same as if no purchase had ever been made.

    However, Monza was not considering the cancellation date when checking if a receipt represented an active subscription.

    This addresses issue #24.

    opened by sidonath 3
  • Set Time.zone_default

    Set Time.zone_default

    Hi! I use this gem in my projects, Thanks!

    But, I have a problem.

    in rails config

    class Application < Rails::Application
      config.time_zone = 'Tokyo'
    end
    

    => set Time.zone_default, so Time.zone access zone_default without current time_zone

    https://github.com/rails/rails/blob/cfb1e4dfd8813d3d5c75a15a750b3c53eebdea65/activesupport/lib/active_support/core_ext/time/zones.rb#L15

    However, in monza config

    Time.zone = Time.zone ? Time.zone : "UTC"
    

    So, monza updates not zone_default but current time_zone. Therefore, even if the I set zone_default is 'Tokyo' in rails config, Time.zone will become 'UTC' in my application.

    Please confirm!

    🙇

    opened by okutaku0507 3
  • Don't depend on last receipt, check max expiration

    Don't depend on last receipt, check max expiration

    Apple does not guarantee the order of receipts within latest_receipt_info, so the active subscription might not be the last one in the array.

    • is_subscription_active? will now check all receipt objects for the max expiration date and check that one
    opened by gabrielgarza 2
  • Add support for

    Add support for "old-style" receipts

    Apple frequently replies to the verify-call with an older receipt format in which various fields that Monza expects are either absent or of a different datatype.

    opened by m-o-e 2
  • silently ignoring errors

    silently ignoring errors

    First of all, thanks for a great, useful library!

    I noticed here:

    https://github.com/gabrielgarza/monza/blob/master/lib/monza/client.rb#L38

    You are silently ignoring an exception. What's the rational here?

    In such cases where I feel pretty confident it's okay, I will still log to STDOUT just in case something goes wrong and i want to audit logs later.

    rescue => e
      puts "error when blah blah: #{e.class}: #{e.message}
      nil
    end
    
    opened by jjb 2
  • data in the receipt-data property was malformed

    data in the receipt-data property was malformed

    I am getting this error when I try to follow the example to call Monza::Receipt.verify in Ruby:

    2017-08-13 01:23:49 - Monza::VerificationResponse::VerificationError - The data in the receipt-data property was malformed.:

    This is the line in the .rb file that causes the error:

    response = Monza::Receipt.verify(data, options)

    I get the base 64 encoded string with the following swift code:

    if let appStoreReceiptURL = Bundle.main.appStoreReceiptURL, FileManager.default.fileExists(atPath: appStoreReceiptURL.path) { do { let receiptData = try Data(contentsOf: appStoreReceiptURL, options: .alwaysMapped) let receiptString = receiptData.base64EncodedString(options: [])

    Then I send the receiptString to the server. What do I need to do to form the receipt data?

    opened by mjpablo23 2
Owner
Gabriel
Gabriel
Swift implementation of KERI (Key Event Receipt Infrastructure)

keri-swift Swift implementation of KERI (Key Event Receipt Infrastructure) Introduction keri-swift is an open source go implementation of the Key Even

WebOfTrust 1 Jun 2, 2022
Passbook gem let's you create pkpass for passbook iOS 6

passbook The passbook gem let's you create a pkpass for passbook in iOS 6+ Installation Include the passbook gem in your project. IE: In your Gemfile

Thomas 232 Jul 27, 2022
In-app purchases and subscriptions made easy. Support for iOS, iPadOS, watchOS, and Mac.

In-app purchases and subscriptions made easy. Support for iOS, iPadOS, watchOS, and Mac.

RevenueCat 1.4k Sep 23, 2022
In App Purchase Manager framework for iOS

InAppFramework In App Purchase Manager framework for iOS Disclaimer I know it's been too long since the last update, quite a few things happened in my

Sándor Gyulai 40 May 23, 2020
iOS SDK for cross-platform in-app purchase and subscription infrastructure, revenue analytics, engagement automation, and integrations

Qonversion is the data platform to power in-app subscription revenue growth. fast in-app subscriptions implementation back-end infrastructure to valid

Qonversion 243 Sep 15, 2022
A jailed in-app purchase cracker for iOS 12.2-15.6

Satella Jailed For, um, educational purposes only or something. Definitely don't use this to pirate in-app purchases in apps to which you don't have l

Lilly 160 Sep 20, 2022
Demonstrates how to integrate Stripe Subscriptions on iOS

Stripe Subscriptions with iOS This example app demonstrates how to integrate Stripe subscriptions with the prebuilt payment UI Requirements Create an

Conjure 0 Nov 26, 2021
Debit/Credit card validation port of the Luhn Algorithm in Swift

SwiftLuhn Warning! This repository is no longer maintained. This is a port of the Luhn Algorithm, generally used for validating debit/credit card deta

Max Kramer 135 Sep 9, 2022
Luhn Credit Card Validation Algorithm

Luhn Algorithm This is a port of the Luhn Algorithm, generally used for validating Credit Card details, to Objective-C (iOS). Swift port can be found

Max Kramer 126 Sep 7, 2022
Luhn Credit Card Validation Algorithm

Luhn Algorithm This is a port of the Luhn Algorithm, generally used for validating Credit Card details, to Objective-C (iOS). Swift port can be found

Max Kramer 126 Sep 7, 2022
Easy to use iOS library with components for input of Credit Card data.

AnimatedCardInput This library allows you to drop into your project two easily customisable, animated components that will make input of Credit Card i

Netguru 38 Aug 13, 2022
card.io provides fast, easy credit card scanning in mobile apps

card.io SDK for iOS card.io provides fast, easy credit card scanning in mobile apps. NEW!!! card.io is now an open-source project! As of December 2014

card.io 2.3k Sep 7, 2022
Easy, drop-in tip jar for iOS apps.

Installation TipJarViewController is available through CocoaPods. To install it, simply add the following line to your Podfile: pod 'TipJarViewControl

Lionheart Software 79 Apr 27, 2022
Easily integrate Credit Card payments module in iOS App. Swift 4.0

MFCard Buy me a coffee MFCard is an awesome looking Credit Card input & validation control. Written in Swift 3. YOoo, Now MFCard is on Swift 5. Swift

MobileFirst 359 Jul 6, 2022
Lightweight In App Purchases Swift framework for iOS 8.0+, tvOS 9.0+ and macOS 10.10+ ⛺

SwiftyStoreKit is a lightweight In App Purchases framework for iOS, tvOS, watchOS, macOS, and Mac Catalyst. Features Super easy-to-use block-based API

Andrea Bizzotto 6.1k Sep 22, 2022
Make and accept payments in your iOS app via Venmo

Venmo iOS SDK The Venmo iOS SDK lets you make and accept payments in your app using Venmo. Installation If you're using CocoaPods: If you don't have a

Venmo 167 Sep 26, 2022
Accept credit cards and PayPal in your iOS app

Important: PayPal Mobile SDKs are Deprecated. The APIs powering them will remain operational long enough for merchants to migrate, but the SDKs themse

PayPal 974 Sep 2, 2022
A lightweight iOS library for In-App Purchases

#RMStore A lightweight iOS library for In-App Purchases. RMStore adds blocks and notifications to StoreKit, plus receipt verification, content downloa

Robot Media 2.4k Sep 12, 2022
A modern In-App Purchases management framework for iOS.

MerchantKit A modern In-App Purchases management framework for iOS developers. MerchantKit dramatically simplifies the work indie developers have to d

Benjamin Mayo 1.1k Sep 27, 2022