I am having multiple questions about this topic
As the title states, I am in need to find the correct hook to bind when an order has been placed and the payment was accepted.
1.) Which hook should I bind in my module when an order has been placed (and payed)?
2.) I am under the impression that there is no generalized hook for this, since some payment methods set the order status to 'payed' automatically (like a successful PayPal transaction) while other methodes require the shopowner to manually set the status to 'payed'. Are there any more than just those two that must be called to cover most cases?
3.) Eventhough I am still hoping that there is a generalized hook, if there's none, how would I approach this issue? Bind "actionPaymentConfirmation" aswell as "displayPaymentReturn" to cover both cases?
4.) Why is the hook "actionPaymentConfirmation" never called when I set the order status to "payed" in the backoffice. My code looks like this
public function install() {
if (!parent::install() || !$this->registerHook("actionPaymentConfirmation")) {
return false;
}
return true;
}
public function actionPaymentConfirmation($params) {
print_r($params); // stepping through with XDebug but the function is never being invoked
}
5.) Does anyone know a free module doing something simmilar I can dig into to get a better idea?
6.) Or might it be easier to override Prestashops core classes to tackle my problems? To break it down, I want to execute stuff after an order has been placed and the status is set to payment was accepted or remotely accepted.
Well, I hope I am not asking to much stuff at the same time, but as you can see, I am interested in mastering these things but have some troubles along the way. Have now been trying and especially search for answers for days now without any luck.
Regards!
I assume that you're with PrestaShop 1.5
1 actionValidateOrder (for new order) & actionOrderStatusPostUpdate (here you can check about the "paid" status)
2 Like 1.
3 Like 1.
4 The hook is actionOrderStatusPostUpdate
5
public function install()
{
return (parent::install()
AND $this->registerHook('newOrder')
AND $this->registerHook('actionOrderStatusPostUpdate'));
}
public function hookNewOrder($params)
{
return $this->hookActionOrderStatusPostUpdate($params);
}
public function hookActionOrderStatusPostUpdate($params)
{
//$params['newOrderStatus'] // after status changed
//$params['orderStatus'] // after order is placed
}
6 Look at 5.
Note: actionValidateOrder the new name (alias) of newOrder
Related
Wondering if anybody in the community has any experience or guidance on how one could use
Authorization decorators (or any custom decoration?)(https://loopback.io/doc/en/lb4/Decorators_authorize.html) on CrudRestController endpoints? (https://loopback.io/doc/en/lb4/Creating-crud-rest-apis.html).
Looked at the src for crud-rest.controller.ts and it just seems like there is no way to really do it.
It seems like it's not easily possible to use any decoration of endpoints in a CrudRestController without taking a very hacky approach and/or wholesale duplicating the code in crud-rest.controller.ts and that we'll have to basically write every endpoint for every model by hand.
Maybe someone has come up with something or has some guidance on an approach? Is the only way to use auth with CrudRestController with the AuthorizationComponent as of now to use Authorizer functions (https://loopback.io/doc/en/lb4/Authorization-component-authorizer.html)
Seems like one part lies in this :
https://github.com/loopbackio/loopback4-example-shopping/blob/9188104c01516a5cbd4ce13f28abe18bafef821e/packages/shopping/src/services/basic.authorizor.ts
/**
* Allow access only to model owners, using route as source of truth
*
* eg. #post('/users/{userId}/orders', ...) returns `userId` as args[0]
*/
if (currentUser[securityId] === authorizationCtx.invocationContext.args[0]) {
return AuthorizationDecision.ALLOW;
}
So I ended up doing :
async authorize(
context: AuthorizationContext,
metadata: AuthorizationMetadata,
) {
const parent = context.invocationContext?.parent
const request = parent?.getBinding("rest.http.request").getValue(parent)
const givenUserId = request?.body?.userId
// next line finds out the user id in the JWT payload
const jwtUserId = context?.principals[0]?.payload?.sub
if (!jwtUserId || (givenUserId && givenUserId != jwtUserId)) {
return AuthorizationDecision.DENY;
} else {
return AuthorizationDecision.ALLOW;
}
}
as my userId is provided in the http parameters (post form or get parameters)
I also use a custom JTWService to read the payload and make it available in the UserProfile.
This may not be the best way to do it, but so far it works. I am still working on finding out how to deal with read requests and add a filter on all of them by userId too using decorators I will post my finding here, if nothing better show up first here.
I created a completionitemprovider for my extension, but now the suggestions based on words in the document aren't shown. Do I have to provide each word in the document?
export class ScriptViewProvider implements vscode.CompletionItemProvider
...
extension.context.subscriptions.push(vscode.languages.registerCompletionItemProvider(
["language"],
this));
...
async provideCompletionItems(document : vscode.TextDocument, position : vscode.Position) : Promise<vscode.CompletionItem[]> {
let completions : vscode.CompletionItem[] = [];
let completion = new vscode.CompletionItem("bla", vscode.CompletionItemKind.Field);
completions.push(completion);
return completions;
}
It brings up "bla" when I type "b", but none of the other words in the document appear.
See this comment of an open VS Code issue.
When your CompletionProvider was registered using a DocumentSelector that is more significant than that of other providers and returns at least one completion item, the other providers are skipped.
The word-based suggestions provider is less significant and so it does not contribute any suggestions.
Depending on the nature of your extension you could try to define a less specific DocumentSelector when registering your CompletionProvider.
If this is not possible I think there is no other option than to providing all completion items yourself (including word-based suggestions).
I'm developing a simple module that hooks to actionUpdateQuantity hook. So, every time the stock of a product is updated I must update the stock of other products.
But, to update the stock I call stockAvailable object, which trigger the actionUpdateQuantity hook. So, I have a endless loop.
Then I tried to manually update the stock directly on the database using SQL, but this have the problem that other modules don't "see" the stock updates. So, modules like MailAlert, ebay or Amazon don't update stock correctly.
I'm a bit stuck here.
How can I update the stock without enter a loop ?
Thanks!
I had similar issue before and think this is not best way but worked for me. Idea is to add class variable in your module:
protected $isSaved = false;
then in hookActionProductUpdate function first check that variable and later after you done saving data change its value
public function hookActionProductUpdate($params)
{
if ($this->isSaved)
return null;
...
$this->isSaved = true;
}
Another way to do this is, in your module when you submit new quantity make sure you also submit product id and attribute id. Then in your hook you can do a check.
public function hookActionUpdateQuantity($params)
{
if ((int)Tools::getValue('id_product') != $params['id_product']
|| (int)Tools::getValue('id_attribute') != $params['id_product_attribute']) {
return false;
}
// do your stuff
}
Everytime the hook actionUpdateQuantity triggers you have a $params array of product whose quantity is being updated.
$params['id_product'] // id of a product being updated
$params['id_product_attribute'] // id of product combination being updated
$params['quantity'] // quantity being set to product
This way your hook will run only once when you are updating quantity of a product from your module (form?). As you update other products quantity they will also trigger this hook but since the data in $params array is different than your POST'ed data, the hook method will return false.
I'm exploring using FluentValidation as it seems to be an elegant API for validation of my ViewModels upon model binding. I'm looking for opinions on how to properly centralize validation using this library as well as from my business (service) layer and raise it up to the view without having 2 different approaches to adding modelstate errors.
I'm open to using an entirely different API but essentially looking to solve this branching validation strategy.
[Side Note: One thing I tried was to move my business method into my FluentValidation's custom RsvpViewModelValidator class and using the .Must method but it seemed wrong to hide that call in there because if I needed to actually use my Customer object they I would have to re-query it again since its out of scope]
Sample Code:
[HttpPost]
public ActionResult AcceptInvitation(RsvpViewModel model)
{
//FluentValidation has happened on my RsvpViewModel already to check that
//RsvpCode is not null or whitespace
if(ModelState.IsValid)
{
//now I want to see if that code matches a customer in my database.
//returns null if not, Customer object if existing
customer = _customerService.GetByRsvpCode(model.RsvpCode);
if(customer == null)
{
//is there a better approach to this? I don't like that I'm
//splitting up the validation but struggling up to come up with a
//better way.
ModelState.AddModelError("RsvpCode",
string.Format("No customer was found for rsvp code {0}",
model.RsvpCode);
return View(model);
}
return this.RedirectToAction(c => c.CustomerDetail());
}
//FluentValidation failed so should just display message about RsvpCode
//being required
return View(model);
}
[HttpGet]
public ActionResult CustomerDetail()
{
//do work. implementation not important for this question.
}
To give some closure to the question (and make it acceptable) as well as summarize the comments:
Business/process logic and validation logic are two entities. Unless the validation ties in to the database (e.g. check for unique entries) there's no reason to group validation into one location. Some are responsible in the model making sure there's nothing invalid about the information, and some handle how the validated values are used within the system. Think of it in terms of property getters/setters vs the logic used in the methods with those properties.
That being said, separating out the processes (checks, error handling, etc.--anything not relating to UI) can be done in a service layer which also tends to keep the application DRY. Then the action(s) is/are only responsible for calling and presenting and not performing the actual unit of work. (also, if various actions in your application use similar logic, the checks are all in one location instead of throw together between actions. (did I remember to check that there's an entry in the customer table?))
Also, by breaking it down in to layers, you're keeping concerns modular and testable. (Accepting an RSVP isn't dependent on an action in the UI, but now it's a method in the service, which could be called by this UI or maybe a mobile application as well).
As far as bubbling errors up, I usually have a base exception that transverses each layer then I can extend it depending on purpose. You could just as easily use Enums, Booleans, out parameters, or simply a Boolean (the Rsvp either was or wasn't accepted). It just depends on how finite a response the user needs to correct the problem, or maybe change the work-flow so the error isn't a problem or something that the user need correct.
You can have the whole validation logic in fluent validation:
public class RsvpViewValidator : AbstractValidator<RsvpViewModel>
{
private readonly ICustomerService _customerService = new CustomerService();
public RsvpViewValidator()
{
RuleFor(x => x.RsvpCode)
.NotEmpty()
.Must(BeAssociatedWithCustomer)
.WithMessage("No customer was found for rsvp code {0}", x => x.RsvpCode)
}
private bool BeAssociatedWithCustomer(string rsvpCode)
{
var customer = _customerService.GetByRsvpCode(rsvpCode);
return (customer == null) ? false : true;
}
}
The following scenario is, I would say quite common and although I know one way of resolving it but it lack elegance.
The example I'm giving is based upon https://github.com/sharparchitecture/Sharp-Architecture-Cookbook.
The application I'm coding is an ASP.NET MVC application and has to support multiple users working on the same object.
The following scenario is an edge case but nevertheless a valid one.
Say you have two users working on the same object and whether the dB row can be updated depends upon the value of a particular field. To make it more concrete, let's say you have a Product and to keep things simple, this Product has "Name" and "QuantityInStock" fields.
Say that initially, there are 10 items of the Product and User1 and User2 want to buy this product. When both users are presented the initial form they are told that there are 10 of these items in stock. Now User1 buys all 10 items while User2 goes to have a coffee. So User1's transaction goes through no problem.
Then User2 comes back after his coffee in the belief that there are still 10 items in stock. So he tries to buy 1 but he must be prevented from doing so since there are no items in stock.
So this problem can be solved by using ASP.NET DataAnnotations validation and this will catch the majority of cases. However, in our edge case, say that User1 and User2 perform the same operation but within a fraction of a second such that when User2 submits the form, it passes the ASP.NET Validation but by the time it gets to the persistence layer, the QuantityInStock is 0.
The solution to this is to perform the validation at the latest moment as possible i.e. just before calling the Update method.
Now for some code.
public ProductModel CreateOrUpdate(ProductModel productModel)
{
var currentProductModel = Get(productModel.Id);
var currentQuantityInStock = currentProductModel.QuantityInStock;
if(currentProductModel.QuantityInStock !=0 && productModel.QuantityInStock >= currentQuantityInStock )
{
currentProductModel.QuantityInStock= productModel.QuantityInStock;
currentProductModel.Name = productModel.Name;
this.productModelRepository.SaveOrUpdate( currentProductModel );
return productModel;
}
else
{
//Raise an exception
}
}
Now, the fact that I'm calling:
var currentProductModel = Get(productModel.Id);
means that if I were to just do this:
this.productModelRepository.SaveOrUpdate( productModel );
would cause an exception:
a different object with the same identifier value was already associated with the session: 1
Hence, I have to copy all of the values from productModel to currentProductModel. It's fine when using something like Automapper but still kind of feels wrong to me in the sense that I feel I should just be able to save productModel as is without having to transfer the data from one object to another.
Moreover, having to do the same validation twice, once using DataAnnotation and another time just before updating violates the DRY principle.
The point is that I feel like I'm missing a trick but don't quite know where to start and what to investigate.
This to me is a simple problem but coming up with a nice elegant solution is something else. So the question is how have you dealt with this simple case in the past? Am I overthinking this?
have you tried optimistic Locking with Version?
// Fluent mapping
public EntitiyMap()
{
OptimisticLock.All(); // all properties musn't be changed in db when saving
// or
OptimisticLock.Dirty(); // only dirty properties musn't be changed in db when saving
}
//
public ProductModel CreateOrUpdate(ProductModel productModel)
{
try
{
// productModel is already validated and updated
this.productModelRepository.SaveOrUpdate( productModel );
return productModel;
}
catch (StaleObjectException)
{
// somebody changed the object in database after we have read it
// Raise an exception or whatever
}
}
Update: i handled such things in another way
public void BuySomething(ProductModel productModel, int amount)
{
int tries = 5;
bool success = false;
while(!success && tries > 0)
{
if (productModel.QuantityInStock <= amount)
{
//Raise an exception
}
productModel.QuantityInStock - amount;
try
{
this.productModelRepository.SaveOrUpdate( productModel );
}
catch (StaleObjectException)
{
// somebody changed the object in database after we have read it
this.productModelRepository.Refresh(productModel);
tries--;
}
}
if (tries <= 0)
{
// Raise an exception or whatever
}
}
zero extra roundtrips if nobody changed it in between, and guaranteed serialisation of the transactions