Open Ralle opened 1 month ago
getOffer() just returns the first offer in the list of offers (which on iOS is generally the only one). Google Play allows multiple offers for the same product. On iOS there will be a single offer. A discount offer is handled a bit differently, it requires a server that generates signed token that gives access to discount offers.
I'm not really sure what you're trying to achieve, can you share your full source code (related to in-app purchases) and log outputs of a purchase session with verbosity set to debug in the plugin.
I am not doing anything particularly exotic. In fact this code worked fine for more than a year but out of the blue it started returning a different offer.
// Setup (This is React)
useEffect(() => {
if (!isNative()) {
return;
}
(async () => {
try {
CdvPurchase.store.verbosity = CdvPurchase.LogLevel.DEBUG;
IAP_PRODUCT_IDS.forEach((productId) => {
const p: CdvPurchase.IRegisterProduct = {
id: productId,
platform: CdvPurchase.Platform.APPLE_APPSTORE,
type: CdvPurchase.ProductType.PAID_SUBSCRIPTION,
};
CdvPurchase.store.register(p);
const p2: CdvPurchase.IRegisterProduct = {
id: productId,
platform: CdvPurchase.Platform.GOOGLE_PLAY,
type: CdvPurchase.ProductType.PAID_SUBSCRIPTION,
};
CdvPurchase.store.register(p2);
});
CdvPurchase.store.error((err) => {
console.log("Store error", err);
errorSender.current.sendError({
name: "CdvPurchase Error",
message: err.message,
stack: "",
time: new Date(),
context: {
code: err.code,
isError: err.isError,
},
});
});
CdvPurchase.store.validator = `${Env.ApiPrefix}/validate-iap-receipt`;
CdvPurchase.store.verbosity = CdvPurchase.LogLevel.DEBUG;
await CdvPurchase.store.initialize([
CdvPurchase.Platform.APPLE_APPSTORE,
CdvPurchase.Platform.GOOGLE_PLAY,
]);
// Load products
const newProducts = new Map(
CdvPurchase.store.products.map((p) => [p.id, p])
);
setProducts(newProducts);
// End load products
// Very important to reload currencies/etc when store is initialized
setLanguageByCode(localStorage.language ?? getBrowserLanguage(), true);
CdvPurchase.store.when().approved(async (transaction) => {
setOrderProcessing(true);
console.log(
`GlobalContextProvider - Transaction approved - transactionId: ${transaction.transactionId}`,
transaction
);
transaction.verify();
});
CdvPurchase.store.when().unverified(async () => {
setOrderProcessing(false);
});
CdvPurchase.store.when().verified(async (verifiedReceipt) => {
setOrderProcessing(false);
const receipt = verifiedReceipt.sourceReceipt;
// A lot of code to verify receipt, etc
});
} catch (e) {
if (e instanceof Error) {
errorSender.current.sendError({
name: e.name,
message: e.message,
stack: e.stack,
time: new Date(),
context: {},
});
}
}
})();
}, []);
// When they click sign up
const product = CdvPurchase.store.get(getPlanAlias(plan));
const offer = await product?.getOffer(
product.platform == CdvPurchase.Platform.APPLE_APPSTORE ? "$" : undefined
); // DEFAULT_OFFER_ID
if (!offer) {
global.trackError({
name: "Native store error",
message: "Cannot find products or offers",
stack: "",
context: {
product,
products: CdvPurchase.store.products,
},
time: new Date(),
});
setError(global.translate("cannot_start_purchase"));
setProcessing(false);
return;
}
console.log("offer.order()");
const purchaseResult = await offer.order();
console.log("offer.order() after");
if (purchaseResult?.isError) {
if (purchaseResult.message === "USER_CANCELED") {
setError(global.translate("purchase_aborted"));
} else {
setError(purchaseResult.message);
}
setProcessing(false);
} else {
setError("");
setProcessing(false);
}
};
It happened again Sep 21:
First thing I noticed: you shouldn't initialize before setting up the events handlers (store.when goes before store.initialize) -- I wonder how that works because, IIRC, the "initialize" promise should only resolve when pending transactions in the queue have been processed (finished, verified or unverified). At the very least, you might be missing some events.
This is the code in the plugin that emits that particular error:
const discountId = offer.id !== DEFAULT_OFFER_ID ? offer.id : undefined;
const discount = additionalData?.appStore?.discount;
if (discountId && !discount) {
return callResolve(appStoreError(ErrorCode.MISSING_OFFER_PARAMS, 'Missing additionalData.appStore.discount when ordering a discount offer', offer.productId));
}
With const DEFAULT_OFFER_ID = '$'
.
_(Side note, you can access that constant as CdvPurchase.AppleAppStore.DEFAULT_OFFER_ID
)_
So somehow offerId
passed is not "$"
... Could you add more logs? (Log the full "product" object) and the "offer" object being ordered?
I will move initialize
to the end and log the whole purchase attempt on the next build. We plan to release it today, so I might be a few days until I have some logs for you.
Thank you for responding.
Observed behavior
From July 14 to Aug 14 we had zero iOS sign ups. I checked my log and everyone had been getting this error:
It was not related to a new version of our app.
It seemed like the order of the offers was wrong. It wasn't getting the default offer. I fixed it this way: Old:
New:
But people still get this error once in a while, only on iOS, confirmed to be the new version of the app with my fix applied.
Any suggestions?