I'm working on an app that uses Apple Pay as a choice in payment method. Normal payment works fine. We have a product involving a subscription, and this works okay as well. We get the apple pay payment info, and then charge the user monthly server-side, using the merchant provider.
We are running into a problem where we have a subscription promo with the first month free. Apple Pay does not seem to support a $0.00 purchase, which would be the first month's cost. Normally we'd receive their credit card details, not charge the first month, but automatically charge monthly after that.
Specifically Apply Pay's PaymentRequest's paymentSummaryItems can't accept a an item with a $0.00 cost, and the total must add up to more than $0.00. Apple pay docs state:
Set the grand total amount to the sum of all the other items in the array. This amount must be greater than zero.
The initWithPaymentRequest call otherwise returns nil, and the modal presentation crashes the app. Error handling could avoid, but it doesn't help get the payment info as needed.
What is the best way to get payment info for a free trial month subscription, if apple pay doesn't accept $0.00 payment?
PKPaymentRequest *paymentRequest = [Stripe paymentRequestWithMerchantIdentifier:APPLE_MERCHANT_ID];
paymentRequest.requiredShippingAddressFields = PKAddressFieldEmail | PKAddressFieldName;
NSDecimalNumber *amount = [Util decimalNumberFromCostStr:cartModel.totals.grandtotalFormattedString];
paymentRequest.paymentSummaryItems = #[
[PKPaymentSummaryItem summaryItemWithLabel:cartModel.totals.grandtotalFormattedString amount:amount],
[PKPaymentSummaryItem summaryItemWithLabel:#"Company Name" amount:amount]
];
if ([Stripe canSubmitPaymentRequest:paymentRequest]) {
PKPaymentAuthorizationViewController *paymentController;
paymentController = [[PKPaymentAuthorizationViewController alloc] initWithPaymentRequest:paymentRequest];
paymentController.delegate = self;
paymentController.modalPresentationStyle = UIModalPresentationFormSheet;
[[Util topMostController] presentViewController:paymentController animated:YES completion:nil];
} else {
[Util alertWithTitle:nil message:#"Cannot submit payment request" delegate:nil];
}
Related
I am implementing inApp features with download hosted content feature. All products are free for unlimited time. Each product have one download associated and that is audio file.
Is possible to start download without user see dialog to buy free product. First user must give his apple id password, than he see dialog to buy free product and than dialog that product is succesfully purchased.Than download can be requested.
But since all products are free can somehow download be reuested without user see confiramtion dialogs and messages and start download imedietly.
If item was previously be purchased/downloaded than user see message that will buy free item again. I know for restore functionalities, but do I need to track previously downloaded items and in that case start download from restore state.
Thanks!
1) Yes, You can download without dialogs. get product detail using store kit, and if product price is free (0), download item without processing purchase request from Storekit.
Code to request product detail:
KProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers: [NSSet setWithArray:productIdentifiers]];
// Keep a strong reference to the request.
self.request = productsRequest;
productsRequest.delegate = self;
[productsRequest start];
// SKProductsRequestDelegate protocol method
- (void)productsRequest:(SKProductsRequest *)request
didReceiveResponse:(SKProductsResponse *)response
{
self.products = response.products;
for (NSString *invalidIdentifier in response.invalidProductIdentifiers) {
// Handle any invalid product identifiers. or check that product you are looking for dozens has its identifier here
}
for (SKProduct *product in self.products)
{
//for example, product you are looking for has identifier "com.product.free"
if([product.productIdentifier isEqualToString:#"com.product.free"] && [product.price compareWithInt:0])
{
// Startdownloading without purchase
}
}
}
2) If you have non-consumable inappt purchase, you must provide option for restore. for downloading, you can write your own logic. check if item is downloaded and re-download only if its not available.
I'm doing the In-App purchase feature. Today I get a weird issue.
I try to get list products by SKProductsRequest. The problem is: sometimes I received invalidProductIdentifiers, but sometimes I received valid products.
SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObjects:objects]];
productsRequest.delegate = self;
[productsRequest start];
Ex: I send a request to get info of 30 products: sometimes get 10 products are valid, sometimes 0, sometimes 30, ... So weird.
In the past, it works well. But one day the problem occurs.
I tried (searched and asked my co-workers) a day but still can not figure out why.
Please help.
Thank you.
First of all check your application id should not be with wild card (*), it cannot be enabled for In-App Purchases so make sure you should create a new App ID.
If above scenario is not your case, then you will have to wait for maximum 24 hours after creating new product id from your developer account, within this time your product id will be fetched from your application. Because some times apple takes time to enabled product ids.
If above both are not your cases then at last you will have to check your product id’s are valid or invalid with below code.
- (void)productsRequest:(SKProductsRequest )request didReceiveResponse:(SKProductsResponse )response {
for (NSString *invalidProductId in response.invalidProductIdentifiers)
{
NSLog(#"Invalid product id: %#" , invalidProductId);
}
}
If you found your product ids are invalid please go through with http://troybrant.net/blog/2010/01/invalid-product-ids/
Cheers.....
Thank you all.
"But one day the problem occurs". And one day, it disappeared.
I think it's the Apple's bug.
If I wipe the data in my app, then re-purchase some managed IAP that I know the account already owns, iOS gives me the native "are you sure you wish to repurchase this item you will not be charged" dialog. That's as expected.
When the purchase returns to my app, I get the receipt with [[NSBundle mainBundle] appStoreReceiptURL]. I then attempt to verify the SKPaymentTransaction's transactionIdentifier with the receipt via my server.
However the receipt's transaction ID for this IAP, because I've already purchased it long ago, is not the same as the SKPaymentTransaction.
How should I be verifying that this is a valid repurchase? Can I get a signed receipt for this repurchase somehow?
All the purchases will be in a single receipt. You do have to check all iAPs there and look for the needed ones.
If you want the id of the original iAP transaction, there is Original Transaction Identifier
For a transaction that restores a previous transaction, the transaction identifier of the original transaction. Otherwise, identical to the transaction identifier.
More information about receipt fields is here: https://developer.apple.com/library/prerelease/ios/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html
Anyway, does it really matter whether this is an original purchase or a repurchase? My checks of this are usually the same.
As far as I understand it you should check the originalTransaction to check that this is valid for transactions that are SKPaymentTransactionStateRestored.
[[NSBundle mainBundle] appStoreReceiptURL] is for the receipt of the main application, not the IAP.
You can use RMStore library, and check all purchases:
RMStoreAppReceiptVerificator *verificator = [[RMStoreAppReceiptVerificator alloc] init];
if ([verificator verifyAppReceipt])
{
NSArray *inAppPurchases = [RMAppReceipt bundleReceipt].inAppPurchases;
for (RMAppReceiptIAP *inAppPurchase in inAppPurchases)
{
NSLog(#"productIdentifier %# originalPurchaseDate %#", inAppPurchase.productIdentifier, inAppPurchase.originalPurchaseDate);
}
}
If your receipt is nil you can refresh receipt:
[[RMStore defaultStore] refreshReceiptOnSuccess:^{
//get purchases
} failure:^(NSError *error) {
}];
I am developing one commercial app (app having different tools to calculate) where I implemented auto-renewable subscriptions. I am able to test it properly.
How my app works:
App web services allow user to use app free for three months.
User is asked to get subscribe for an year to use tools.
Once payment is done, receipt is validate and stored at web services.
Once subscription period get collapsed, auto renewable subscription come to play.
My questions:
Is adding restore button is any way compulsory? Referring Is restore button necessary for Auto-renewable subscriptions?, it says restore button is not required in auto subscription.
After payment; receipt is been passed to web server and web server validate receipt. Is their any mechanism to get expiry date from receipt or with the help of receipt?
Thanks!
For
query 1: Currently i am stock to
Is restore button necessary for Auto-renewable subscriptions?
solution.
and for query 2 i found that i can extract below details from
receipt:
NSString *kReceiptBundleIdentifier = #"BundleIdentifier";
NSString *kReceiptBundleIdentifierData = #"BundleIdentifierData";
NSString *kReceiptVersion = #"Version";
NSString *kReceiptOpaqueValue = #"OpaqueValue";
NSString *kReceiptHash = #"Hash";
NSString *kReceiptInApp = #"InApp";
NSString *kReceiptOriginalVersion = #"OrigVer";
NSString *kReceiptExpirationDate = #"ExpDate";
NSString *kReceiptInAppQuantity = #"Quantity";
NSString *kReceiptInAppProductIdentifier = #"ProductIdentifier";
NSString *kReceiptInAppTransactionIdentifier =
#"TransactionIdentifier";
NSString *kReceiptInAppPurchaseDate = #"PurchaseDate";
NSString *kReceiptInAppOriginalTransactionIdentifier =
#"OriginalTransactionIdentifier";
NSString *kReceiptInAppOriginalPurchaseDate =
#"OriginalPurchaseDate";
NSString *kReceiptInAppSubscriptionExpirationDate = #"SubExpDate";
NSString *kReceiptInAppCancellationDate = #"CancelDate";
NSString *kReceiptInAppWebOrderLineItemID = #"WebItemId";
As per my knowledge on inapppurchase .. Auto-renewable subscription is acceptable for magazines ..like this only ... see the guidelines... I kept Auto-renewable for my app like webservices ...they reject my app and they suggest me to keep non-renewable subscriptions.. Now coming up to storing the receipt in your webserver .. how you can store your receipt in your webserver? based on the appleid we want to store the receipt .. but apple doesnot provide User appleid ...because all transactions is based on apple id only..i think storing the receipt is not the right way...You can keep Restore button to restore the previous transactions .. in these restore process you can get the previous transactions like product id and the transaction receipt .. you can pass this receipt to the storekit framework . then you will get the response 0 or 21006 like this based on the response you want to unlock the content...
I am using nSoftware to interact with QuickBooks. My requirement is to update customer's credit card expiry month and year only. Code used for this is
nsoftware.InQB.Customer cust = new nsoftware.InQB.Customer();
cust.GetByName("test");
cust.CreditCard.ExpMonth = customer.CreditCardItem.CardExpMonth;
cust.CreditCard.ExpYear = customer.CreditCardItem.CardExpYear;
cust.Update();
Problem is GetByName method returns customer object which has credit card number like "xxxxxxxxxxxxxx1234". Updating customer object updates actual credit card number with xxx....1234. My requirement is to update only expiry month and year.
Dev Environment:- ASP.Net 4.0, C#
Modifying the credit card fields and calling the Update method will cause all of the card fields to be sent to QuickBooks, including the "xxxxxxxxxxxx1234" card number. In this case, specifying a new QBCard object can be done to make sure that only the credit card fields that you explicitly intend to update are sent to QuickBooks.
So, something like this should do the trick:
nsoftware.InQB.Customer cust = new nsoftware.InQB.Customer();
cust.GetByName("test");
QBCard card = new QBCard();
card.ExpMonth = customer.CreditCardItem.CardExpMonth;
card.ExpYear = customer.CreditCardItem.CardExpYear;
cust.CreditCard = card;
cust.Update();
Please let me know if this works for you.