Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Receipt verification overhaul #7

Closed
wants to merge 44 commits into from

3 participants

@lxcid

Proposed actions

  • Treats status code 21006 as valid.
  • Handle status code 21007 and 21008. When receive such notifications, switch to the appropriate Apple's receipt verification server apple suggested by the status code.
  • Fixed In-App Purchase vulnerability.
  • Supports auto-renewable subscriptions.
  • Peep into transaction to determine the client/environment.
  • Better error reporting. (Based on Error Handling Programming Guide)
  • Validate with transactionReceipt in addition to validating with transaction (Allows for transaction receipt to be stored for later validation)
  • Unit Tests.
  • In Apple's implementation, Apple stores every transaction ID (in user defaults) and check against repeating transaction ID. Currently we implemented 2 blocks (one for checking the transaction ID and the other for saving the transaction ID) with the option to set the default implementation similar to Apple's one. This is not on by default though.

Benefit from the changes

  • More secure client side receipt verification.
  • Library able to figure out the environment to use during development and production without the developer intervention.
  • Support for auto-renewable subscriptions receipt verification.
  • The library is environment aware and does not requires any preprocessor guard for development and production. The library automatically choose the correct client based on the transaction, and if it is wrong for some unknown reason, it knows how to retry the request with the right client.

Canceled actions

  • Treats status code 21004 as a serious problem by raising an exception.
    I decides against it because anyone who tested it and face this problem will already be alert by the failure flow.

To do

Notable changes/points

  • Removed _receiptVerificationClient. Introduce sandboxReceiptVerificationClient and productionReceiptVerificationClient singletons. Singleton was chosen because of my lack of knowledge of creating a thread safe lazy loading property. I thought through about it. Maintaining a _receiptVerificationClient ivar will requires making its get and set thread safety, thus removing it and go for a different implementation seems like a better and simpler choice.
  • The reference implementation that Apple provides does seems like it doing some duplicated actions but it did not though. A explanation of it actions below:
    1. It first compare the SKPaymentTransaction and its SKPayment matches its purchase info.
    2. Then checks its signature and purchase info is valid.
    3. After which it sent the receipt to Apple's receipt verification server.
    4. Upon receiving a response. It check if the response receipt matches the purchase info.

Pending issues

  • The Security.framework is not optional yet though. The current implementation doesn't seems to work. It seems that the _SECURITY_SECBASE_H_ is already defined with or without linking Security.framework.
  • The code size have grow from 300+ to 900+ lines. Is there anyway we can reduce the code size?
  • Because we now peek into transaction to determine the client to use, we are deserializing the receipt twice in our operation.

Food for thoughts

  • Response from Apple's receipt verification server for auto-renewable subscription is different from non-consumer. But the success block only returns the receipt key. I returns the response object instead of just receipt on success instead.

Reference

lxcid and others added some commits
@lxcid lxcid The request should be a POST to iTunes verifyReceipt API end point in…
…stead of a GET.
bb9695f
@lxcid lxcid Expands verify transaction interface to support password, needed for …
…verifying auto-renewable subscription receipt.
9ee5aa3
@lxcid lxcid Parameter encoding should be set to JSON for receipt verification cli…
…ent.
d52abd7
@lxcid lxcid Handles exception message in receipt verification failure optionally …
…to prevent crash when a failure does not give exception message. (e.g. 21007)
b17302e
@lxcid lxcid extendedValidation might be false and error will not be set. Follow m…
…ore closely to to `ValidationController` in this aspect.
5bd2c1e
@lxcid lxcid Adds receipt validation. f4a8526
@lxcid lxcid Implements `doTransactionDetailsMatchPurchaseInfo:withPurchaseInfo:`.
This version differs from Apple's implementation in that it checks for all optional cases except `requestData`.
The reason `requestData` is avoided is because this is a field Apple reserve for future uses and thus its behaviour is subject to change. Guarding against it will make the code less future proof.
A detail explanation is available as inline comments.
0cebded
@mattt Adding note about mission-critical sister projects b4488a0
@lxcid lxcid Specify code sections. 4c0095e
@lxcid lxcid Differentiates between production and sandbox receipt verification ba…
…se URL string.

Mark sandbox receipt verification base URL string as unused for now.
79417b4
@lxcid lxcid If transaction receipt is nil, validation fails. 824ef69
@lxcid lxcid Extends `verifyPurchase:` with error parameter (`verifyPurchase:error…
…:`). Elevate the errors during parsing plist data upwards.
078365d
@lxcid lxcid Remove unnecessary comments/commented codes.
This code was available in `VerificationController` because it is using a delegate-based system and need a centralise place to pass information.
As CargoBay is using block-based system. This requirement is non-existent.
ce785f2
@lxcid lxcid Explains some of my thoughts about storing transaction IDs. Open for …
…discussion.
3dd74a8
@lxcid lxcid Makes CBCheckReceiptSecurity optional too. (Although I have trouble g…
…etting optional portion to work. Apple seems to include it regardless of whether you add the Security.framework or not.)
bd82fb9
@lxcid lxcid Should validates the values against purchase info instead of transact…
…ion receipt.
cba6354
@lxcid lxcid Replace if else with switch for status control flow. e5830ea
@lxcid lxcid Handles status 21007 and 21008 by replacing `_receiptVerificationClie…
…nt` and retrying.
bbfe51d
@lxcid lxcid Hints on an alternative of discovering the environment early. 41d3d9f
@mattt
Owner

Most of this came from that one example from Apple, right?

In 62ebde5, I incorporated some of the functionality from that file, but heavily refactored to better fit the project. Am I wrong in thinking that some of this commit duplicates what my previous commit added? For instance, where Apple implemented -isTransactionAndItsReceiptValid:, I had CBValidateTransactionMatchesReceipt(), or are those functionally different?

@lxcid

Hi mattt,

I review the codes and will attempts to explain better their purpose. I please ignore my previous comments though. I was doing the code review in bed when i woke up.

First of all, the purchase-info decoded from SKPaymentTransaction is almost identical to the receipt returned from Apple's receipt validation response, with the exception of the use of - in key for the former and the use of _ for the later.

I documented them down in https://gist.github.com/4180678

doTransactionDetailsMatchPurchaseInfo is done before actually invoking receipt validation server. (I will explains the full call flow below). It main purpose is to check SKPaymentTransaction and its SKPayment value to match the purchase-info.

CBValidateTransactionMatchesReceipt main purpose is to make sure that the receipt returned by Apple's receipt verification server matches the purchase-info of the transaction.

CBValidateTransactionMatchesReceipt is more of a mirror function to doesTransactionInfoMatchReceipt.
isTransactionAndItsReceiptValid (which calls doTransactionDetailsMatchPurchaseInfo later) is more of a mirror method to isTransactionAndItsReceiptValid.

In my implementation verifyPurchase seems redundant though.

The actual flows for verifying receipt is something along this line:

  1. verifyPurchase: (verifyPurchase:error:)
    This is the root method to call for verify purchase.
    1. isTransactionAndItsReceiptValid: (isTransactionAndItsReceiptValid:error:)
      1. Decoding theTransaction.transactionReceipt
      2. isTransactionIDUnique: (NULL)
        Verify if the transaction ID is unique. It seems no receipt should have the same transaction ID from the code. Not sure about how to best do this portion.
      3. checkReceiptSecurity() (CBCheckReceiptSecurity())
        Verify the receipt purchase info and signature integrity.
      4. doTransactionDetailsMatchPurchaseInfo:withPurchaseInfo: (doTransactionDetailsMatchPurchaseInfo:withPurchaseInfo:)
        Verify the receipt purchase info and SKPaymentTransaction and SKPayment matches.
    2. Start verification with Apple's receipt verification server.
    3. connection did receive data...
    4. doesTransactionInfoMatchReceipt: (CBValidateTransactionMatchesReceipt())
      The initial part of doesTransactionInfoMatchReceipt: does JSON decoding and status checking etc. We already done that in the block.
      This mainly check that the receipt return from Apple matches purchase-info.

I think there's refactoring needed to be done but functionality wise, there shouldn't be any duplication code for my review though. They serves different purposes.

@lxcid

I want to note that the currently 21007 and 21008 handling involve swapping out the default HTTP client. This is potentially not thread safe.

I was thinking of refactoring the main method to take in the client something like

- (void)verifyTransaction:(SKPaymentTransaction *)transaction
                   client:(AFHTTPClient *)client
                 password:(NSString *)password
                  success:(void (^)(NSDictionary *receipt))success
                  failure:(void (^)(NSError *error))failure

Makes sandboxReceiptVerificationClient and productionReceiptVerificationClient returns singleton or property object that are lazily initialised.

_receiptVerificationClient continue to point to the default ones. Default initialise client that talk to the production server and swap upon the default at first hint of sandbox.

So in an app in the app store, the sandboxReceiptVerificationClient should never have to initialise (There will be no hint of .

While in testing and development, the library automatically swap the default to sandbox. Require no preprocessor block to conditional hardcode this value. result in smother development experience and safer validation behaviour for app in the app store.

The only key is that we have to reduce code coverage for @synthesized(self) block when swapping out the default.

Thus exposing client as parameter will make that part of code more thread safe.

I'll be working on this tonight. You could cherry pick them out if you find a better implementation.

I'm thinking of something along this line when swapping:

@synchronized (self) {
    _receiptVerificationClient = [self sandboxReceiptVerificationClient];
}

For the method, I have 2 suggestions...

// property
- (AFHTTPClient *)sandboxReceiptVerificationClient {
    // This lazy initialization is not thread safe. And we can't use @synchronized(self) here as it will deadlock with the above code, unless we introduce some generic object to maintain its sanity.
    if (_sandboxReceiptVerificationClient == nil) {
        AFHTTPClient *theHTTPClient = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:kCargoBaySandboxReceiptVerificationBaseURLString]];
        [theHTTPClient setDefaultHeader:@"Accept" value:@"application/json"];
        [theHTTPClient registerHTTPOperationClass:[AFJSONRequestOperation class]];
        [theHTTPClient setParameterEncoding:AFJSONParameterEncoding];
        [AFJSONRequestOperation addAcceptableContentTypes:[NSSet setWithObject:@"text/plain"]];
        _sandboxReceiptVerificationClient = theHTTPClient;
    }

    return _sandboxReceiptVerificationClient;
}

and...

// Singleton
- (AFHTTPClient *)sandboxReceiptVerificationClient {
    static AFHTTPClient *theHTTPClient = nil;
    static dispatch_once_t theOnceToken;
    dispatch_once(&theOnceToken, ^{
        theHTTPClient = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:kCargoBaySandboxReceiptVerificationBaseURLString]];
        [theHTTPClient setDefaultHeader:@"Accept" value:@"application/json"];
        [theHTTPClient registerHTTPOperationClass:[AFJSONRequestOperation class]];
        [theHTTPClient setParameterEncoding:AFJSONParameterEncoding];
        [AFJSONRequestOperation addAcceptableContentTypes:[NSSet setWithObject:@"text/plain"]];
    });
    return theHTTPClient;
}

The property way is cleaner () but accessing the method will add some overhead and maintaining thread safety is a pain.

The singleton one seems most pain free and thread safe. at the expense of introducing another singleton object inside another singleton object.

Sorry I know I'm increasing the code complexity of CargoBay. I'm willing to iron out the issue and make this as lean as possible.

lxcid added some commits
@lxcid lxcid This introduce 2 singleton (sandboxReceiptVerificationClient and prod…
…uctionReceiptVerificationClient) and remove the swapping of _receiptVerificationClient in status 21007 and 21008.

This create a better multithread environment but at the expense of a dumb API that always try to hit production when it knew it is in sandbox environment.
01ac031
@lxcid lxcid Removes `_receiptVerificationClient`. On the start of verifying recei…
…pt, instantiate (if needed) the required singleton client (sandbox or production). I find this implementation most thread safe when it comes to switching.
f682a45
@lxcid lxcid Remove `verifyPurchase:error:` indirection as it seems to have been r…
…educed into a proxy in our implementation.
52e106d
@lxcid lxcid Supposedly also support verifying restored transaction. 73f72ba
@lxcid

Just some updates.

I'll try to keep it brief. I'm starting to worry that my changes is getting really hard for you to follow and digest and be merged in near future. Also I'm spreading the information between 2 posts. I'll do some trimming of the information I posted to keep them all within here instead. Probably by today.

I documented down the responses from Apple's receipt verification server in gists. I'll keep the header post as the main update board.

Recently changes including:

  • Remove _receiptVerificationServer. Introduce 2 singletons for production and sandbox HTTP client and lazily loaded when needed (Check the transaction receipt for hint on which server the receipt is meant for). Uses singleton pattern because it is the most thread safe way that i know of, but would prefer if it would be lazy loaded as a property of CargoBay in a thread safe manner.
  • Reduce redundant indirection method verifyPurchase:error:.
  • Allow restored receipt to be verified too.
@lxcid

Hi mattt,

Sorry for the constant update. I have edit the first post and summarise the current situation. Hope it helps.

lxcid added some commits
@lxcid lxcid Fix logic error. Should be if transaction state not purchased or rest…
…ored.
19d5ab8
@lxcid lxcid Streamline the code for checking receipt security. 75c26d3
@lxcid lxcid Refactoring.
 - Refactored `doTransactionDetailsMatchPurchaseInfo:withPurchaseInfo:` to `CBValidateTransactionMatchesPurchaseInfo()`.
 - Branched `CBValidatePurchaseInfoMatchesReceipt()` to `CBValidateTransactionMatchesPurchaseInfo()`. It was split for allowing the `CBValidatePurchaseInfoMatchesReceipt()` to be reused.
83df886
@lxcid lxcid Refactoring.
 - Extract part of `isTransactionAndItsReceiptValid:error:` logic into CBPurchaseInfoFromTransactionReceipt().
9d7ea95
@lxcid lxcid Attempts to verify transaction that are not purchased or restored is …
…classified as an failure.
aa913e4
@lxcid lxcid - Validate latest receipt if available.
 - Extract the logic that validate transaction receipt. This allows the user to store transaction receipt for future validation.
 - Because the response object may contains more than just receipt (e.g. latest receipt information), I have decides to pass down the response object on success instead of just receipt.
183fa98
@lxcid lxcid Always returns error object upon failure, describing the the failure …
…cases. (Based on Error Handling Programming Guide)
c34027f
@lxcid lxcid Continue to improve error reporting. 9f45251
@lxcid lxcid Exposes transaction receipt verification method. a776b13
@lxcid lxcid Minor formatting to match convention. 529adb6
@lxcid lxcid Merge branch 'master' of https://github.com/mattt/CargoBay into recei…
…pt-verification-overhaul
d104cd8
@lxcid lxcid Could not use CBError* because it is used by Core Bluetooth. Rename t…
…o CargoBayError*. Also rename CBStatus* to CargoBayStatus*.
209473f
@lxcid lxcid Continue to improve error reporting. 9e81aec
@lxcid lxcid Check for transaction ID uniqueness is supported but optional currently.
User can replace with their own implementation or uses the default implementation based on Apple's VerificationController.
53774c7
@lxcid lxcid Do not check against bundle version because it will likely fails if t…
…he app is updated.
2e1cde0
@lxcid lxcid Should uses com.mattt instead of me.mattt. cc178ad
@lxcid lxcid if you compile your source with iOS 6 SDK, __IPHONE_6_0 will always b…
…e defined. This is not the same as (__IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_5_1).
83e02d7
@lxcid lxcid Fix CBValidateTransactionMatchesPurchaseInfo using wrong error code. 0dff19a
@lxcid lxcid Fixed a critical bug on CBDataFromBase64EncodedString where the inner…
… loop when reconstructing the value start from index 0 instead of the current index. (I'm surprised that this have worked well so far…)
25e9734
@lxcid lxcid Unit tests for receipt verification (Pretty much the only functionali…
…ties that can be unit test it seems).

Had to exposes some of the method, but I tried to keep them in a separate header as private method for now.
I also tries to bind some of the C functions so that I could run unit test on them too.
I find this is the least intrusive.
6e25c2d
@lxcid

Hi mattt,

I have written the unit tests to test against the transaction verification against sandbox receipt data. They should all passes. The test should be run on iOS 6 (Had some issues with iOS 5.1 can't use dispatch semaphore which is required to test networking stuff).

This version should be more or less ready for your review. I'll try to do documentations now and see if I can hack up an example demo (hopefully If I can figure out a way).

Currently, the only worry is that it is using hardcoded intermediate certificate to verify the signature, the certificate that usually come with the signature expires by 2014 and the hardcoded intermediate certificate expires at 2016.

Before the signature certificate expires, Apple will have to update to a new certificate which might or might not be signed by the the hardcoded intermediate certificate by then. So there's a chance this can cause major validation failure scenario for people using this library (and VerificationController) at that point of time.

Although this failure does not just happens to us, it also happens to anyone who implements the validation correctly (People who had implemented VerificationController on server or on client) thus I believe apple will take extra care in handling this situation. But we have a disadvantage of having to go through Apple Review process and require affected user to update the app in order for this to be fixed.

I thought about checking against the current time and disable the check if the certificate has expired. But this can create a potential loophole where user can set their system clock to change the behavior of this library. Checking the purchase date isn't a good choice either as we couldn't verify that the receipt is compromise.

The signature check is very important to prevent people from modifying the transaction receipt, as you can see from my unit tests I made a few adjustments to it so that I can mimic some real world scenario. Without the signature check, the receipt can be compromised easily.

Currently, we have to warn the user that it is their responsibility to keep their app updated with this library and we try to keep a look out for Apple instruction and update accordingly.

Otherwise, all is good for this version. :) I'm probably not making anymore change to the API interface.

Except that I might be interested in exposing the signature validation method as I see potential uses for people who do server side validation.

@lxcid lxcid Refactors `productionReceiptVerificationClient` and `sandboxReceiptVe…
…rificationClient`.

Uses `dispatch_once` in an uncommon way. Rather than static local variables of their methods, it is an ivar of the `CargoBay` instance. It should lazily instantiate the `AFHTTPClient` but allows the client to be owned by the instance of `CargoBay`.
c416a33
@mattt
Owner

It took a while, but I finally finished integrating all of your awesome changes into master, which is now tagged at version 0.2.0.

Most of my changes were cosmetic, renaming and reformatting to fit the project coding conventions. FWIW, I think variables starting with the went out of mainstream convention with the rise of underscored ivars in custom initializers. Also, user-facing strings should always be wrapped in some kind of NSLocalizedString. For a vendored library, I prefer NSLocalizedStringFromTable

I did change a few things about the public API:

  • Rather than having two nearly-identical methods each for verifying transaction and verifying its receipt with and without a password, I removed the versions without a password. The password parameter is named passwordOrNil to hint accordingly.
  • Transaction ID uniqueness verification is now just one block, wherein the user is responsible for persisting any IDs in that block, along with returning YES or NO. This allows the default behavior to be cleaned up to exist as an inline code block, which is executed if the verification block is nil.
  • I removed -productsWithRequest:, as it didn't seem to provide any significant benefit over the built-in StoreKit request--at least not enough to justify the increased surface area to the API. Products should either be fetched with StoreKit or AFNetworking only; the choice is an unnecessary bit of complexity.
  • For the tests, I got around the private category solution by declaring extern function pointers to the necessary implementation functions. Those methods could be replaced with just the functions, but I left that as it was for now.

All in all, I'm very happy about how everything turned out, and extremely excited by how much better CargoBay is now for it. With this newest version, I can say with some degree of confidence that CargoBay is now the best library for working with StoreKit. Thanks so much for your help, Stan :D

@mattt mattt closed this
@lxcid

Hi mattt,

Thanks for taking the time to review and merge this! I'm so sorry for all the formatting you have to go through with my weird convention. I'll be reviewing them soon so as not to give code reviewers/project masters a hard time in the future.

I felt the same too. I felt CargoBay is the best library to work with StoreKit at the moment. I tried it in my project and it quite enjoyable. I hope this give other users a great foundation and experience to build their in app purchases upon.

I look through them and the changes you made are great. The API felt a lot nicer with your input and experience. Thanks once again for the in depth review you made.

I'm excited with this release of the new version of CargoBay! :D

Cheers and Happy New Year.

@markrickert

WOW! Great job, guys! Can't wait to try using this in a new app! So far I've always used MKStoreKit, but I've been following this development for a while, and it looks like it's probably production-ready now.

@mattt
Owner

@markrickert Thanks! That's really encouraging to hear, Mark. I'd appreciate any feedback you might have migrating from MKStoreKit. Honestly, I couldn't make heads or tails of it myself, but if there's anything you think is missing, I'd be happy to look into it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Nov 19, 2012
  1. @lxcid
  2. @lxcid

    Expands verify transaction interface to support password, needed for …

    lxcid authored
    …verifying auto-renewable subscription receipt.
  3. @lxcid
  4. @lxcid

    Handles exception message in receipt verification failure optionally …

    lxcid authored
    …to prevent crash when a failure does not give exception message. (e.g. 21007)
Commits on Nov 21, 2012
  1. @lxcid

    extendedValidation might be false and error will not be set. Follow m…

    lxcid authored
    …ore closely to to `ValidationController` in this aspect.
  2. @lxcid

    Adds receipt validation.

    lxcid authored
Commits on Nov 27, 2012
  1. @lxcid

    Implements `doTransactionDetailsMatchPurchaseInfo:withPurchaseInfo:`.

    lxcid authored
    This version differs from Apple's implementation in that it checks for all optional cases except `requestData`.
    The reason `requestData` is avoided is because this is a field Apple reserve for future uses and thus its behaviour is subject to change. Guarding against it will make the code less future proof.
    A detail explanation is available as inline comments.
Commits on Nov 29, 2012
  1. @lxcid

    Specify code sections.

    lxcid authored
  2. @lxcid

    Differentiates between production and sandbox receipt verification ba…

    lxcid authored
    …se URL string.
    
    Mark sandbox receipt verification base URL string as unused for now.
  3. @lxcid
  4. @lxcid

    Extends `verifyPurchase:` with error parameter (`verifyPurchase:error…

    lxcid authored
    …:`). Elevate the errors during parsing plist data upwards.
  5. @lxcid

    Remove unnecessary comments/commented codes.

    lxcid authored
    This code was available in `VerificationController` because it is using a delegate-based system and need a centralise place to pass information.
    As CargoBay is using block-based system. This requirement is non-existent.
  6. @lxcid
Commits on Nov 30, 2012
  1. @lxcid

    Makes CBCheckReceiptSecurity optional too. (Although I have trouble g…

    lxcid authored
    …etting optional portion to work. Apple seems to include it regardless of whether you add the Security.framework or not.)
  2. @lxcid
  3. @lxcid
  4. @lxcid
  5. @lxcid
Commits on Dec 1, 2012
  1. @lxcid

    This introduce 2 singleton (sandboxReceiptVerificationClient and prod…

    lxcid authored
    …uctionReceiptVerificationClient) and remove the swapping of _receiptVerificationClient in status 21007 and 21008.
    
    This create a better multithread environment but at the expense of a dumb API that always try to hit production when it knew it is in sandbox environment.
  2. @lxcid

    Removes `_receiptVerificationClient`. On the start of verifying recei…

    lxcid authored
    …pt, instantiate (if needed) the required singleton client (sandbox or production). I find this implementation most thread safe when it comes to switching.
Commits on Dec 2, 2012
  1. @lxcid

    Remove `verifyPurchase:error:` indirection as it seems to have been r…

    lxcid authored
    …educed into a proxy in our implementation.
  2. @lxcid
Commits on Dec 8, 2012
  1. @lxcid
  2. @lxcid
  3. @lxcid

    Refactoring.

    lxcid authored
     - Refactored `doTransactionDetailsMatchPurchaseInfo:withPurchaseInfo:` to `CBValidateTransactionMatchesPurchaseInfo()`.
     - Branched `CBValidatePurchaseInfoMatchesReceipt()` to `CBValidateTransactionMatchesPurchaseInfo()`. It was split for allowing the `CBValidatePurchaseInfoMatchesReceipt()` to be reused.
Commits on Dec 9, 2012
  1. @lxcid

    Refactoring.

    lxcid authored
     - Extract part of `isTransactionAndItsReceiptValid:error:` logic into CBPurchaseInfoFromTransactionReceipt().
  2. @lxcid

    Attempts to verify transaction that are not purchased or restored is …

    lxcid authored
    …classified as an failure.
  3. @lxcid

    - Validate latest receipt if available.

    lxcid authored
     - Extract the logic that validate transaction receipt. This allows the user to store transaction receipt for future validation.
     - Because the response object may contains more than just receipt (e.g. latest receipt information), I have decides to pass down the response object on success instead of just receipt.
Commits on Dec 10, 2012
  1. @lxcid

    Always returns error object upon failure, describing the the failure …

    lxcid authored
    …cases. (Based on Error Handling Programming Guide)
Commits on Dec 11, 2012
  1. @lxcid
  2. @lxcid
  3. @lxcid
  4. @lxcid

    Merge branch 'master' of https://github.com/mattt/CargoBay into recei…

    lxcid authored
    …pt-verification-overhaul
  5. @lxcid

    Could not use CBError* because it is used by Core Bluetooth. Rename t…

    lxcid authored
    …o CargoBayError*. Also rename CBStatus* to CargoBayStatus*.
  6. @lxcid
  7. @lxcid

    Check for transaction ID uniqueness is supported but optional currently.

    lxcid authored
    User can replace with their own implementation or uses the default implementation based on Apple's VerificationController.
  8. @lxcid
Commits on Dec 14, 2012
  1. @lxcid
Commits on Dec 17, 2012
  1. @lxcid

    if you compile your source with iOS 6 SDK, __IPHONE_6_0 will always b…

    lxcid authored
    …e defined. This is not the same as (__IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_5_1).
Commits on Dec 18, 2012
  1. @lxcid
Commits on Dec 19, 2012
  1. @lxcid

    Fixed a critical bug on CBDataFromBase64EncodedString where the inner…

    lxcid authored
    … loop when reconstructing the value start from index 0 instead of the current index. (I'm surprised that this have worked well so far…)
  2. @lxcid

    Unit tests for receipt verification (Pretty much the only functionali…

    lxcid authored
    …ties that can be unit test it seems).
    
    Had to exposes some of the method, but I tried to keep them in a separate header as private method for now.
    I also tries to bind some of the C functions so that I could run unit test on them too.
    I find this is the least intrusive.
Commits on Dec 26, 2012
  1. @lxcid

    Refactors `productionReceiptVerificationClient` and `sandboxReceiptVe…

    lxcid authored
    …rificationClient`.
    
    Uses `dispatch_once` in an uncommon way. Rather than static local variables of their methods, it is an ivar of the `CargoBay` instance. It should lazily instantiate the `AFHTTPClient` but allows the client to be owned by the instance of `CargoBay`.
Something went wrong with that request. Please try again.