I have a problem on the in app purchase cordova plugin by fovea.
I am kinda confused.
What I want to do, is that when the user chooses a product (monthly subscription), I do what I need to do, process payment and all that jazz, and when everything is done, I save an entry in my database, to indicate that the user is subscribed (and some more info).
However, when I use it, I see that I have not one but at least 10 saved entries. For same user, same product.
I have no idea why it does that. So I guess something is wrong with my code. As I use Sandbox for testing IOS side, sometimes the pop up just doesn't appear (where you need to enter your password and confirm your purchase), and yet I have 10/20 entries saved in my database (I put that bit of code when product is owned, then in the .finished event).
Can someone help me?
Here is the code
let produit = null;
if (!(window as any).store) {
alert('Store indispo');
}
this.store.register({
id: this.valeurEnvoi.app,
alias: 'abonnement',
type: store.PAID_SUBSCRIPTION,
});
console.log(this.valeurEnvoi.app);
this.store.refresh();
this.store.ready( () => {
produit = this.store.get(this.valeurEnvoi.app);
this.envoiLogs('récupération du produit', 'NO ID');
if (produit.canPurchase) {
this.store.order(produit);
}
});
this.store.refresh();
// this.store.manageSubscriptions();
this.store.when(produit).updated( (p) => {
if (p.owned) {
} else {
}
});
this.store.refresh();
console.log(this.store.log);
this.store.error( error => {
this.testLog = error.message;
alert('erreur: ' + error.code + ' message: ' + error.message);
});
this.store.when(produit).approved( (order) => {
order.finish();
});
this.store.refresh();
this.store.when(produit).finished( (order) => {
const test = this.store.findInLocalReceipts(produit);
alert(test.transactionId);
this.confirmerAchatMobile();
});
Also, If I could have some pointer to get the transactionID, that would be great! Thank you!
If you see any strange stuff, let me know, it will help.
I took a look at the repo. refresh() appears to be deprecated:
/**
* #deprecated - use store.initialize(), store.update() or store.restorePurchases()
*/
refresh() {
throw new Error("use store.initialize() or store.update()");
}
source: https://github.com/j3k0/cordova-plugin-purchase/blob/master/www/store.js
Also, from a comment I infer that the transactions are replayed if you call refresh multiple times, could that cause your issue?: Notice that all previous transactions will be replayed if you call store.refresh() a second time in the lifetime of your app.
source: https://github.com/j3k0/cordova-plugin-purchase/issues/1298
There are quite a few (109) issues on the git repo. So I think you might find your answer there. If not, I'd create an issue there, the maintainers are probably more likely to have an answer for you.
Related
I would like to use transaction in wolkenkit-eventstore when saving events to eventstore and be able to rollback those events if something else fail, is it possible ?
I saw in source code (in saveEvents method) that you are releasing connection pool:
try {
const result = await connection.query({ name: `save events ${committedEvents.length}`, text, values });
for (let i = 0; i < result.rows.length; i++) {
committedEvents[i].event.metadata.position = Number(result.rows[i].position);
}
} catch (ex) {
if (ex.code === '23505' && ex.detail.startsWith('Key ("aggregateId", revision)')) {
throw new Error('Aggregate id and revision already exist.');
}
throw ex;
} finally {
connection.release();
}
at the finally step, so i can't gain this connection pool in any way.
Is there any way i can do transaction based system with wolkenkit-eventstore ?
I'm one of the core developers of wolkenkit, so first of all thanks for bringing up this question 😊
Right now what you want is actually not possible, but nevertheless it could be a good idea to support this use case.
In wolkenkit the procedure is that the command handler publishes the events, and only if the command handler succeeds, the events are stored in the event store in an all-or-nothing approach.
To be able to understand your use case better – you said, you would like:
to rollback those events if something else fail[s]
What would this "something else" be?
Since this could be the start for a longer discussion, I think StackOverflow is probably not the perfect place to do this, so if you would like to talk to us about this feature, could you please open a feature request for this?
I do load aittable records via their API:
const base = airtable.base(item.baseId);
base("Dishes")
.select({
})
.eachPage(
function page(records, fetchNextPage) {
tableRecords.push(...records);
// To fetch the next page of records, call `fetchNextPage`.
// If there are more records, `page` will get called again.
// If there are no more records, `done` will get called.
fetchNextPage();
},
function done(err) {
if (err) {
console.error(err);
return;
}
console.log("##Done", tableRecords.length);
}
);
and as a result, I receive 2202 records. But in the table in UI I do see 2271 records. And when I do export to csv - I see the same 2271 as well.
Code is pretty basic, I even remove view setting to ensure, that it's not a presentational issue.
Google did not help me (nothing related). Did anyone face the same issue? Any solution?
NB: for sure I already compared both lists and found items I do miss, but while observing those items I see nothing special there. So it says me what I do miss, but not why
const base = airtable.base(item.baseId);
base("Dishes")
.select({})
.all()
I am using the realtime database and I am using transactions to ensure the integrity of my data set. In my example below I am updating currentTime on every update.
export const updateTime = functions.database.ref("/users/{userId}/projects/{projectId}")
.onUpdate((snapshot) => {
const beforeData = snapshot.before.val();
const afterData = snapshot.after.val();
if (beforeData.currentTime !== afterData.currentTime) {
return Promise.resolve();
} else {
return snapshot.after.ref.update( {currentTime: new Date().getTime()})
.catch((err) =>{
console.error(err);
});
}
});
It seems the cloud function is not part of the transaction, but triggers multiple updates in my clients, which I try to avoid.
For example, I watched this starter tutorial which replaces :pizza: with a pizza emoji. In my client I would see :pizza: for one frame before it gets replaced with the emoji. I know, the pizza tutorial is just an example, but I am running into a similar issue. Any advice is highly appreciated!
Cloud Functions don't run as part of the database transaction indeed. They run after the database has been updated, and receive "before" and "after" snapshots of the affected data.
If you want a Cloud Function to serve as an approval process, the idiomatic approach is to have the clients write to a different location (typically called a pending queue) that the function listens to. The function then performs whatever operation it wants, and writes the result to the final location.
I just ran into a problem where I am not sure how to solve.
Background: I've got an App with two views:
1st one to input a number,
2nd one to see the details.
After the view switched to the detail view, I would call the bindElement() to get my data from the backend.
_onRoutePatternMatched: function(oEvent) {
// ...
this.getView().bindElement({
path: "/EntitySet('" + id+ "')"
});
},
Problem is that the ID is quite often the same, hence, the method will call the backend only if the ID is different from the last call.
So I tried to solve the problem by using the following:
this.getView().getModel().read("/EntitySet('" + id+ "')",{
success: function(oData, response) {
that.getView().setModel(oData, "");
}
});
By this, the data is always up to date. But now the binding is a bit different.
Binding with bindElement():
{
"id": "1234",
"propety1": "abc",
// ...
}
Binding with setModel() and id = 1234:
{
"EntitySet('1234')": {
"id": "1234",
"propety1": "abc",
// ...
}
}
For the first way, my binding looked like this:
<ObjectHeader title="{id}">
Now, it would have to look like this:
<ObjectHeader title="{/EntitySet('1234')/id}">
And here I have a problem, because the value of id (in this case 1234) will always be different and so the binding won't work. I can't bind directly to the ObjectHeader, because I will need some properties from the model later. That is the reason I am binding to the view so that all that remain available.
My idea was to edit the binding inside the success method of the read method. I would like to delete the surrounding element. Do you have an idea, how to do this? Or even a simpler/better idea to solve my pity?
Edit:
I forgot to mention the refresh method. This would be possible, but where do I have to put it? I don't want to call the backend twice.
Simply call the API myODataModel.invalidateEntry(<key>) before binding the context in order to retrieve the latest data.
// after $metadata loaded..
const model = this.getOwnerComponent().getModel("odata");
const key = model.createKey(/*...*/) //See https://stackoverflow.com/a/47016070/5846045
model.invalidateEntry(key); // <-- before binding
this.getView().bindElement({
path: "odata>/" + key,
// ...
});
From https://embed.plnkr.co/b0bXJK?show=controller/Detail.controller.js,preview
invalidateEntrydoc
Invalidate a single entry in the model data.
Mark the selected entry in the model cache as invalid. Next time a context binding or list binding is done, the entry will be detected as invalid and will be refreshed from the server.
A simple question about the getting started (Album) tutorial.
Its' about the deleteAction :
public function deleteAction()
{
$id = (int) $this->params()->fromRoute('id', 0);
if(!$id){
return $this->redirect()->toRoute('album');
}
$request = $this->getRequest();
if ($request->isPost()) {
$del = $request->getPost('del', 'No');
if($del == 'Yes'){
$id = (int) $request->getPost('id');
$this->getAlbumTable()->deleteAlbum($id);
}
return $this->redirect()->toRoute('album');
}
return array(
'id' => $id,
'album' => $this->getAlbumTable()->getAlbum($id)
);
}
From what I understand, when an album is deleted from the database, a redirection occurs just after that, to the /album/ route. If I comment (suppress) that redirection, an error "could not find row $id" occurs because getAlbum($id) then tries to retrieve the album that was just deleted, and thus no longer exists...
My question is : is there a way (like a conditional statement on the return array() or getAlbum()) to make things work without the redirection (which should aim at a success page btw)?
Thanks !
You need to understand the code. Don't blindly copy paste, understand what's happening there. What you're asking for makes literally no sense.
Once an album is deleted you should see an overview page or a "deleted success" page. This is completely for you to decide what you want to choose but in all cases the redirection is the way to go. There's still the forward() plugin, but all it does is to do the redirect internally. There's not really any advantage in doing this for the given use-case that you present.
If you want to return something else than a redirect, then by all means go ahead and return another ViewModel that points to a different template.
TL/DR: understand the code before you want to modify it.