(I could have sworn there was an issue for this, but I can only find a 7.x one that I don't want to hijack.)
Rough notes:
- the availability checker for ownership of a grantable gets in the way
- we probably still need to prevent re-purchase of a license product early on in the license's lifetime. That suggests a setting on the PV to say 'renewals may be purchased within interval X from expiry'.
- the availability checker thus needs to know whether there is a license, as well as the grantable. The logic becomes:
- got license? yes:
- is it unlimited? may not purchase
- are you in the renewal period? yes: may purchase
- got license? no:
- do you have the grantable? yes: may not purchase
- the cart should show a clear message that the user is purchasing a renewal, and what date they are extending to
- something needs to place the existing license on the order item in the new order
- the order sync class needs to know it's a renewal, and at the point where it would normally activate, extend instead
- the code that deletes a license when an order item is deleted needs to account for this process, and NOT delete the license
- tests. all the tests.
Tests that needs to be done:
For a non renewable license: you can't renewFor a renewable license:- buy a license
- complete the order
- buy the same license in the renewable window
- complete the order
- see that the expires has been augmented of the license duration
For a renewable license: test that you can't renew outsite the renewable windowFor a renewable license:- buy a license
- complete the order
- buy the same license in the renewable window
- stop at the cart state
- remove the product from the cart
- see that the license in the active state and the expire time has been untouched
Comment | File | Size | Author |
---|---|---|---|
#148 | allow-repurchase-diferent-PV-2943888-148.patch | 5.02 KB | junaidpv |
#141 | reroll-commerce_license-allow-renewal-2943888-141.patch | 50.12 KB | robotjox |
Issue fork commerce_license-2943888
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
- 2943888-allow-renewal changes, plain diff MR !5
Comments
Comment #2
GrimreaperHello,
First, thanks for your work on this module. I have just retested the 8.x-2.0-alpha8 and it works well.
I am also interested in this feature request for the French Drupal association https://github.com/Drupal-FR/site-drupalfr.
I don't think we will go on the commerce recurring module because of what I see it is more for automated billing and more complex. Also I think having people manually buying their membership before expiration should be a possibility.
Comment #3
宁皓网_王皓 CreditAttribution: 宁皓网_王皓 commentedThank you for the really awesome work : )
Comment #4
pmaguniaHere is a patch to get started with. It's just a rough draft but it did work in my use case.
It requires the patch from Recurring Period which I just submitted which is referenced here.
Comment #6
pmaguniaTrying to get the test passed.
Comment #8
joachim CreditAttribution: joachim as a volunteer commentedThanks for having a go at this. It's always nice to see new people working with this module :)
There are a few problems with your approach though:
> It requires the patch from Recurring Period which I just submitted which is referenced here.
I had a quick look at that patch. It shouldn't be in that module -- RP should not know anything about licenses. It's just a system to produce recurring dates on a schedule.
I think it makes more sense to extend the license entity rather than create a new one.
There's already this field! Are you maybe working with an old version of the module?
If the user already has a license, then grantLicense shouldn't be called.
Overall, I suggest you look at the outline I posted in the issue summary and implement it that way (unless you spot mistakes in my plan, of course!).
Comment #9
pmaguniaThank you for your quick feedback!
I will have another go at this.
The reason I used
BundleFieldDefinition
is for the life of me I couldn’t get the value of product variation in the License Class in the presave method.Also, Above you mention the grantable:
Can you please explain what exactly is the grantable?
Comment #10
joachim CreditAttribution: joachim as a volunteer commented> I couldn’t get the value of product variation in the License Class in the presave method.
I have a patch I've been working on for another issue which adds a getter for that. I'll try to remember to commit just that part of it tomorrow when I'm at my work machine so you can use that.
> Can you please explain what exactly is the grantable?
The 'grantable' is the thing the user owns or has access to because they have a license. So, e.g., a role, or an OG membership entity, or a field value on a node. There is an interface for checking that on license plugins.
Comment #11
joachim CreditAttribution: joachim as a volunteer commented> I have a patch I've been working on for another issue which adds a getter for that. I'll try to remember to commit just that part of it tomorrow when I'm at my work machine so you can use that.
Just pushed that commit: 20a22c6b25a8ed42b286c5a527cbd5edf18d8b31
Comment #12
pmagunia@joachim Sorry it has taken me awhile to get back to you. Thank you for committing that getter.
One question I had is where should I add the checkbox and date field to allow a renewal within a specified time period?
I thought the the form on the Product type setting would be a good place to add it.
Comment #13
joachim CreditAttribution: joachim as a volunteer commented> I thought the the form on the Product type setting would be a good place to add it.
IIRC we already add some settings to the product variation type? In which case, that might be a good place, as the work of saving the extra options is already there.
Comment #14
pmaguniaYes, I think you are right. It is the product variation type. I was able to get this together tonite.
Its not a full patch but something to get started with. For some reason I can't save the interval value.
I wanted to hide the interval when
allow_renewal
was unchecked but its it not being hidden.If anyone else would like to jump in feel free.
Comment #15
joachim CreditAttribution: joachim as a volunteer commentedUse sentence case rather than title case for these.
The description is just repeating the label. Say something like: "Allows a customer to renew their license by re-purchasing the product for it."
"The interval before the license's expiry during which re-purchase is allowed. Prior to this interval, re-purchase is blocked, as normal."
The name value here is wrong -- you need the full chain of parents of the form element.
Comment #16
pmaguniaThank you for those suggestions. I've incorporated them into this new patch.
I think everything works except that when a license is renewed, a duplicate license is issued.
I've tried deleting it with
$this->delete()
but then I get an error message that a string is not translatable.I believe the Renew column is being set correctly on the original license though.
Any suggestions would be welcome.
Comment #17
joachim CreditAttribution: joachim as a volunteer commentedGlad to hear you've got the UI working!
I don't think that is true, as you're not changing the conditions on this if() block.
This isn't the right approach at all. We shouldn't be getting this far!
When the user places the order, you need to find the existing license, and attach it to the order item.
You are then working with just one license, and here you simply need to extend its expiry date.
Looks like your IDE is breaking Drupal coding standards...
That's not the license type, it's the expiry type. The license type is something like 'role'.
Comment #18
pmaguniaI had a quick question regarding one set of your comments above.
I was going to move this logic to the
LicenseAvailabilityCheckerExistingRights.php
file. I'm guessing the existing license gets attached to$entity
or$this
.If so, which class variable do we attach it to or replace?
Comment #19
joachim CreditAttribution: joachim as a volunteer commented> I was going to move this logic to the LicenseAvailabilityCheckerExistingRights.php file. I'm guessing the existing license gets attached to $entity or $this .
No, in that class, PurchasableEntityInterface $entity is the product variation. That is not something that the customer can change by making a purchase!
The license gets created in LicenseOrderSyncSubscriber and set on the order item when the order is placed. But LicenseOrderSyncSubscriber also allows for the possibility of the license already being set on the order item.
So you need to step in earlier than that, to 1. load the existing license, and 2. set it on the order item. So probably on the add to cart event.
See also these items in my rough outline above:
- the cart should show a clear message that the user is purchasing a renewal, and what date they are extending to
- something needs to place the existing license on the order item in the new order
- the order sync class needs to know it's a renewal, and at the point where it would normally activate, extend instead
- the code that deletes a license when an order item is deleted needs to account for this process, and NOT delete the license
Comment #20
pmaguniaHad another go at it. This was working on my server though I haven't added any functionality to account for deletions.
I wanted to throw something out there though just to make sure I was going in the right direction.
Comment #22
pmaguniaHad to pull the changes from dev first.
Comment #24
pmagunia@joachim, did you have comments about my last patch?
I know it failed testing but I was wondering if I was on the right track or if you had any other comments.
Comment #25
joachim CreditAttribution: joachim as a volunteer commentedSorry for the delay in getting back to you. This is a very quick pre-breakfast review, apologies if I've skimmed over things.
Why is this getting removed?
We're adding a lot of functionality here, so the class name becomes incorrect.
The code is a bit messy here, and needs a fair bit of cleaning up -- see comments below.
Comment and code don't match: the existing rights checker does not check for a license.
This code doesn't seem to be useful - nothing's being done with these strings.
Are you sure this is the right place to do this? There is a setting for licenses to be activated on order place rather than validation.
Why is this file getting deleted?
Comment #26
joachim CreditAttribution: joachim as a volunteer commentedOk, having now had breakfast, here's some still pretty rough ideas:
- I'd like LicenseAvailabilityCheckerExistingRights to remain, but obviously, it needs to know to step out of the way if the product is configured to allow renewals.
- LicenseMultiplesCartEventSubscriber should be unchanged. It has a simple job to do, involving only the count of things in the cart.
- we need a new event subscriber, to act on onCartEntityAdd. This is what should check if a repurchase is allowed, and if so, it should take care of finding the existing license and setting it on the $order_item.
- we probably need to consider what happens if a user adds a renewable product to their cart, then goes away for days, and comes back to their cart after expiry, and other similar considerations. That's probably handled by checking order refresh.
- LicenseOrderSyncSubscriber needs to find the license on the order item, and know to extend the license. This gets a bit hairy, because I don't think we can or should allow the 'Activate the license in the 'place' transition' system to be used in this case -- because what do you do if the order is cancelled? You'd need to subtract a year from the expiry and it just gets too messy. Also, the whole point of 'Activate the license in the 'place' transition' was to give the user access immediately for payment gateways that have a delay, and in a renewal, the user already has access. So basically, the logic is that if the license is already active, AND the order state is complete, then extend the license period.
- extending the license period should be done with a new method on the License entity, rather than making the expiry date method public.
Comment #27
GrimreaperHello,
Thanks all for your work on this issue, I have tested patch from comment #22.
I works but there are some warnings.
On the complete step when it is the first time a license is created for the user for a product:
And when selecting a product on which you already have a license, there is still the message "Renewals of this license are not allowed before expiration" even though the product is added to the cart.
Is there anything I can do to help resolve the issue. I saw @joachim review. Maybe you can guide me a little bit to do the points.
Also a question, this new bahavior will be for all licences. Would it be better to have a settings per licence to allow this behavior?
Comment #28
joachim CreditAttribution: joachim as a volunteer commented> Also a question, this new bahavior will be for all licences. Would it be better to have a settings per licence to allow this behavior?
No, the intention is definitely that it will be configured on each product, so not for all licenses.
> Is there anything I can do to help resolve the issue. I saw @joachim review. Maybe you can guide me a little bit to do the points.
If you have time to help push this forward that would be great, though @pmagunia might be planning to work on it too, so maybe wait until everyone's responded to my comments to coordinate.
Comment #29
GrimreaperThanks for your reply,
Ok, I though the behaviour would be set sitewide but if it can be set per product it's good.
Ok I'll wait for reply.
Comment #30
GrimreaperHi,
@joachim: I am at Drupal Europe. Would it be possible to sprint together (or at least having a brefing on this issue) to get this issue done?
Comment #31
joachim CreditAttribution: joachim as a volunteer commentedYup, I’ll be there, and I’m even doing a session on License!
I will be at the contribution day on Friday, so can help you with this then.
Comment #32
GrimreaperOk,
I am already there and I am registered on the spreadsheet: https://docs.google.com/spreadsheets/d/1OxtAsRs_SeKQynL5bJ_5AEmRInLdyG7m...
I will attend your session.
Until friday, can you give me direction on what needs to be done please? If you prefer to see that directly, it's ok. I have other subjects to sprint on.
Comment #33
joachim CreditAttribution: joachim as a volunteer commentedIf you want to get started today, have a look at comments #25 and #26. The most recent patch here is a good start, but makes a number of wrong turnings.
I get in late tonight, will be at the conference from tomorrow morning. Happy to have a chat at morning coffee or lunchtime!
Comment #34
joachim CreditAttribution: joachim as a volunteer commentedYay working on this at DrupalEurope!!!
Rough plan. These run in the following order.
- LicenseAvailabilityCheckerExistingRights needs to know to step out of the way if the product is configured to allow renewals. Otherwise it acts as normal to prevent a repurchase.
- New class: LicenseRenewalCartEventSubscriber. This queries for the user's existing license, and sets it on the order. This needs to run before LicenseOrderSyncSubscriber.
- LicenseOrderSyncSubscriber needs to know when and whether to extend the license.
Comment #35
Grimreaper@joachim,
Ok, I found why the die() where not executed...
With the patch, the service LicenseAvailabilityCheckerExistingRights has been removed so after cancelling code deletion, we needed to clear the cache for Drupal to detect it exists again...
Comment #36
Grimreaper- LicenseAvailabilityCheckerExistingRights needs to know to step out of the way if the product is configured to allow renewals. Otherwise it acts as normal to prevent a repurchase.
=> OK
In commerce_license/src/LicenseAvailabilityCheckerExistingRights.php :
I will go to a session, but I need to discuss a little with you for the content of LicenseRenewalCartEventSubscriber.
Comment #37
joachim CreditAttribution: joachim as a volunteer commentedWe were talking about this step yesterday:
- New class: LicenseRenewalCartEventSubscriber. This queries for the user's existing license, and sets it on the order. This needs to run before LicenseOrderSyncSubscriber.
- LicenseOrderSyncSubscriber needs to know when and whether to extend the license.
The problem here is that LicenseOrderSyncSubscriber needs to know that the license on the order item should be renewed. But how?
- checking the license is already active isn't enough, as we come to LicenseOrderSyncSubscriber for multiple transitions, and some later ones can have a just-purchased license with active state
- we could save a flag on the entity, but that feels fiddly
- use the state field
Out of those, on reflection, I think using the state, as @Grimreaper suggested, is the cleanest. It does mean adding an extra state, but there is a symmetry to the initial purchase and the renewal purchase:
Initial purchase: license created in 'new' state -> goes to 'active' state
Renewal purchase: license moved to 'renewing' state -> goes to 'active' state
That does mean that a failed order needs to move 'renewing' back to 'active', same way a failed initial order has to clean up the license.
Comment #38
Grimreaper@joachim,
Sorry, by "state", I meant the key/value API. Not a state in the state machine.
Do you still think a state of the state machine should be done?
Comment #39
joachim CreditAttribution: joachim as a volunteer commentedYes, a state on the license. I don't think using key/value is necessary, as we already have a content entity that can carry the data and is in the right place to do that.
Comment #40
joachim CreditAttribution: joachim as a volunteer commentedQuick thoughts on refactoring of the code being worked on at the moment at DrupalEurope:
- move the query for loading an existing license for the current user & a given product variation to the LicenseStorage class / interface
- move the code to determine if a given license entity will allow a renewal repurchase to a method on the License entity
Comment #41
GrimreaperHere is a WIP with comment 40 taken into account.
Comment #42
GrimreaperAnother patch where I worked into the LicenseRenewalCartEventSubscriber.
The right license is selected but after the addition to the cart. The cart is empty.
I think I will need to discuss a little bit with you @joachim.
Comment #43
joachim CreditAttribution: joachim as a volunteer commentedThis is really starting to come together!
That type doesn't look right, as the form element for this is an 'interval'.
Change the method name to canRenew(), as isRenewable() makes me thing it's just about checking the 'allow_renewal' setting.
Use the current time service, so that we can mock it in tests.
Do we need to check expiration time is less than now?
That makes me think though -- we should check the state is active at the top of this method.
add 'at this time' to the end of the description.
Need to check renewal is allowed first.
I think we need to save the order item here.
LicenseOrderSyncSubscriber saves order items when it makes changes to them.
Let's take an $account parameter here.
And also, we can bail early if $account->uid == 0, as licenses are never anonymous.
Can we change that variable name to have _ids at the end.
Comment #44
GrimreaperThanks for the review!
Comment #45
GrimreaperOK, I found why I have an empty cart...
"You already have the member role. You may not purchase the adhésion product." => commerce_license/src/Plugin/Commerce/LicenseType/Role.php
Someone else is emptying it.
Comment #46
joachim CreditAttribution: joachim as a volunteer commented> I can't because I need a object that has the "sub" function to remove the timestamp interval. (and I didn't found how from the interval object print the corresponding timestamp)
Like this:
> I have used an uid instead of an account object to be aligned with the createFromProductVariation method. Do you still want an account?
Hmm good point, not sure.
> "You already have the member role. You may not purchase the adhésion product." => commerce_license/src/Plugin/Commerce/LicenseType/Role.php
*sigh*... :)
Comment #47
GrimreaperOK I found why LicenseAvailabilityCheckerExistingRights was still in the way. See attached patch and interdiff.
Comment #48
GrimreaperOtherwise there will be a fatal error.
Now we need to see how to properly extends license expiration time.
Comment #49
GrimreaperAlmost done.
The problem I have to extend the expiration timestamp is that in commerce_license/src/EventSubscriber/LicenseOrderSyncSubscriber.php, at the end of onCartOrderTransition method:
So the activate transition is triggered first so in License presave method:
$this->original->state->value is equal to pending and not renewal_in_progress.
And the other thing to handle is remoing item from the cart.
Comment #50
GrimreaperAnnnddd!!! Done.
Thanks for your help @joachim.
Renewing is ok. Removing an order item is handled.
Now it needs a (final) review and automated tests.
Comment #51
joachim CreditAttribution: joachim as a volunteer commentedTests might have to wait, as the branch is failing: #2999402: tests are failing.
Can't we just move it back to 'active'?
I'd rather we call this 'renewing', to match order states.
This should just go to 'active'.
Urgh no sure what to call this.
That's the wrong transition -- the name doesn't make sense. We need a 'cancel renewal' transition.
Comments for classes and methods should be in 3rd person, so 'Handles...' / 'Sets...'
Comment #52
GrimreaperHere a patch that at least fixes the existing tests.
Point 6 from previous comment taken into account.
As seen together the problem is that we need the transition because we do not have more info in the presave method.
Also a query on order item referencing the license won't work because of this feature. We would have the order items from the previous orders that will reference the license.
Comment #53
GrimreaperTests that needs to be done:
Comment #54
Grimreaper@joachim,
I am currently in spectrum C (thunder sprint room). If we can meet to see how to do the tests, it would speed up closing this issue.
Comment #55
GrimreaperHere are WIP tests.
We already found 3 bugs.
We need to figure out why the license is not updated.
Putting the list of tests to implement in the issue summary.
Comment #57
GrimreaperHello,
Here is a new patch that provides the remaining tests.
The tests implying cart manipulation still fail for an unknown reason. The order item is not the right one or not well detected.
I have updated the issue summary.
Comment #58
GrimreaperDebugging the calls.
It is after the cart save that the order item is removed and it seems because of a bug in the existing license detection.
Because the existing license is in the "renewal_in_progress" state.
I continue testing and will provide a new patch by the end of the day of it is a success.
Comment #59
GrimreaperI made the tests passing but after a last manual tests I found bugs.
When renewing a license the user lost is role.
The expiration time of the license is not updated correctly (but the tests about that are green...) Searching why.
Comment #60
GrimreaperOk.
I found out why it was not working anymore, it was because in my manual tests I used another order type than the default one.
I have added a message when trying to renew outside the renewal window for better UX.
@joachim: only stay the "problem" when in the renewal process the user lost the license grant. Is it a problem?
Thanks for the review.
Comment #62
GrimreaperFix tests due to the addition of the drupal_set_message in License.php.
Comment #63
joachim CreditAttribution: joachim as a volunteer commented> @joachim: only stay the "problem" when in the renewal process the user lost the license grant. Is it a problem?
Doesn't sound good... can you explain more?
(And bear in mind I have forgotten EVERYTHING we discussed at DrupalEurope :) )
We shouldn't set messages here, as this could be made by API calls.
It's up to the caller (which knows what sort of context it's in) to set the message.
Also, the message service should be used rather than dsm().
... here is where we should show the user a message along the lines of:
'You have an existing existing license for this product [TODO: link here once there is user admin UI for licenses!]. This will be extended by PERIOD when you complete this order.'
Comment #64
Grimreaper@joachim, Thanks for the review.
Here is a new patch where I moved the drupal_set_message where you asked.
Also I had chosen License.php canRenew method to know directly if the license was renewable and to get the license renewal window start.
About the lost of the license grant in the renewal process:
As soon as you add a product in your cart, if you already have a license that you can renew, this license is used in the order item and saved to have its state changed into "renewal in progress" state.
But in License.php::preSave(), has the state is changed and not set to "active" we fall into:
And so the grant (in my use case, the role) is removed.
Comment #65
joachim CreditAttribution: joachim as a volunteer commentedSo you lose the grant during a renewal purchase? That's definitely a problem.
License.php::preSave() should know about the 'renewal in progress' state.
*sigh* This is getting SOOOO complicated :(
Comment #66
joachim CreditAttribution: joachim as a volunteer commentedI'm almost wondering whether sticking the existing license on the order item at the point is too soon, and we should instead somehow mark the order item and only pick up the license at order completion.
Or add a 2nd ref field to the order item for 'renewing_license' and move the license from that to the normal license field at checkout.
Will ponder.
Comment #67
Grimreaper@joachim: Thanks for your feedback. I will wait for your feedback on the best architecture for this feature.
Comment #68
joachim CreditAttribution: joachim as a volunteer commentedRandom thought I had yesterday... not had time to reread everything here, so could be crazy, feel free to point out flaws :)
We introduced 'renewal in progress' state because when an order is cancelled, we need to know which of these cases holds:
- this was a brand new license, and the license should be abandoned and cancelled
- this was a renewal, and the license should stay active
My thought was that to distinguish between these two states, we could instead do one of these:
- see whether older, closed orders refer to the license
- see whether the license's activation timestamp is older than the order being deleted
Comment #69
GrimreaperHi joachim,
Hum why not.
So with this solution, no need for the new state renewal in progress. OK.
I don't know when I will have the time to remake a patch.
Comment #70
drupgirl CreditAttribution: drupgirl commentedHi. To me this is the most valuable of all featured requests, as it falls into the most basic of use case: I have a role license, I want to be able to renew it before it expires so I continue to get all the fun features I bought without a day of missing out.
The patch is working for me: my user gets the state of "Renewal in Progress" with the correctly updated expires date. (Although I will be continuing to test this heavily throughout the day and night.)
This patch provides a great alternative to having to be forced to use Recurring in order to let users continue with their level of site access without a day of interrupted service, as they cannot add role licenses to carts when they already have that role. This module should be able to handle renewals without the aid of Recurring, imo.
Please help me to understand how to get this feature request into the module, as the module is somewhat unusable on its own without it due to the glitching of not being able to reup before expiration.
Thank you so much.
Comment #71
joachim CreditAttribution: joachim as a volunteer commented> Please help me to understand how to get this feature request into the module, as the module is somewhat unusable on its own without it due to the glitching of not being able to reup before expiration.
If you've got time to move this issue forward, that would be great!
Everything should be in the comments... collectively, they hold more information about this issue than I do, as it's been ages since I've worked on this module and I've forgotten everything. If things aren't clear, or there are gaps, post questions here and I'll try to remember!
Comment #72
calbasiHi,
Last patch don't just extend the time of the existent license, but create a new license. Then, after renewal, you have 2 licenses, one that is going to expire soon. And the second that will expire later (the new one).
But, when expiration date of the old license arrive:
- this license becomes expired AND its user receives a mail message warning about it (even when he has a second license, what is renewing thew old and now expired license).
- if the license is granting an user role, when the old license becomes expired, its user loose his role.
I think both are not designed behaviors and the second is, in fact, a major issue.
Comment #73
Kojo Unsui CreditAttribution: Kojo Unsui commentedHi there,
I had a look at patch 64, made a bunch of testing, and AFAIK in my local version now, most of the renewable feature seem to be ok, with very few changes.
All of the listed tests above that need to be done are passed successfully.
I then changed messages as following :
- replaced all
drupal_set_message
withDrupal::messenger()
.- added a "renewalStartTimeMessage" when the license is renewable but out of the renew dates window...
- in that same case, removed the NotPurchasableMessage (
You may not purchase the @product-label product.
)- added a "You have an existing license for @product-label until @expires-time. This will be extended until @extended-date when you complete this order."
if ($existing_license && $existing_license->canRenew())
Still to be done IMHO for renewable licenses :
- when a product variation type providing a license has « Activate license when order is placed » parameter set to true, expire time is not updated :
=> when licence added to cart, state changes to renewal in progress
=> when order in completed with payment, state changes back to active
=> but expire time is not updated & validating order afterwards does not update expire time neither
- add in the README that orders that contain a license have to be validated before renewing them, otherwise if you’re in the renewable window and purchase it again, this will create a duplicate instead
- or add a contrainst to prevent these dupes ?
- remove
Drupal\commerce_order\Plugin\Validation\Constraint message @product-label is not available with a quantity of @quantity.
(in fact this one is not specific to renewable licenses)Thank you.
Comment #74
Kojo Unsui CreditAttribution: Kojo Unsui commentedHere's a patch, based on @Grimreaper #64 one.
Comment #75
Kojo Unsui CreditAttribution: Kojo Unsui commentedFix CartManagerTestTrait fatal error : it has been moved to
Drupal\Tests\commerce_cart\Traits\CartManagerTestTrait
.(Todo it is deprecated, not sure how to rewrite the test)
Fix (hopefully) most coding standards messages.
Comment #76
Kojo Unsui CreditAttribution: Kojo Unsui commentedMy bad, I forgot a couple of CartManagerTestTrait wrong use declarations. Provides a new one.
Comment #78
Kojo Unsui CreditAttribution: Kojo Unsui commentedLooked at the tests results :
- some report a problem similar to this Commerce Stripe issue about :
- other failed tests refer to an existing issue : Tests broken for 8.x-2.x.
I may not pollute the current issue with these. The provided patch IMHO fixes most of current "renewable license" issue and I hope will be tested / reviewed soon.
When testing the feature, please remember that : orders that contain a license have to be validated before renewing its license, otherwise if you’re in the renewable window and purchase it again, this will create a duplicate.
Comment #79
Kojo Unsui CreditAttribution: Kojo Unsui commentedMy version now ships also with following patches.
After some more days developing and testing, I can confirm that all tests listed are passed, with @grimreaper work and patch 76 above.
So I'm going to stick with this for now and push to production.
Comment #80
pmaguniaWow Thanks for the patch Kojo.
I can confirm its working in my local dev environment.
It did not create duplicate licenses with my own limited testing.
The patch applied cleanly to the latest dev version.
Comment #81
pmaguniaI'm not sure this is correct but I think this updated patch solves all of the testing problems except one. Most of them were related to missing modules.
I wasn't sure how to fix the empty array but I thought I would throw what I have out there to get the ball rolling.
Comment #83
pmaguniaI forgot to include some untracked files in the last patch.
Comment #84
pmaguniaComment #86
pmaguniaThe test needed to bypass the access check to query storage.
Comment #87
Kojo Unsui CreditAttribution: Kojo Unsui commentedWow (my turn ;-) thanks for handling the tests failure, Pmagunia !
I tested on a fresh install of demo project, plus License 2.x-dev and #86 applies smoothy.
I performed a few tests :
- Set up product, variations, orders and all the stuff...
- Purchased normal product, fine. Purchased non renewable license, fine. Purchased renewable license, fine.
- Finally re-purchased the same one in the renewable window. Message informing the license will be renewed is displayed, and the license is extended. I tried with Interval based on reference date then with rolling interval. I tried both with admin and anonymous accounts.
IMHO, RTBC.
Comment #88
Drupal Centric CreditAttribution: Drupal Centric commentedGreat work. I've tested on a product with a renewable license and rolling interval and all it worked fine. Renewal was allowed and interval extended correctly.
core: "8.9.1"
commerce: "2.x-dev",
commerce_cart_advanced: "^1.0@beta",
commerce_cart_flyout: "^1.7",
commerce_cart_redirection: "^2.1",
commerce_license: "2.x-dev",
commerce_paypal: "1.x-dev",
commerce_reports: "1.x-dev",
commerce_shipping: "2.x-dev"
Comment #89
Joao Sausen CreditAttribution: Joao Sausen at Dexa commentedI'm using the patch on #86 on a project and its working fine. Thanks!
Comment #90
fy1128 CreditAttribution: fy1128 commentedHow to avoid the queue job that was created before expiring the license that had been renewed?
Comment #91
fy1128 CreditAttribution: fy1128 commentedPatched the AdvancedQueue plugin 'commerce_license_expire'.to do not expire the license that was renewed before the queue being processed.And I think the expire time should be re-calculated from now if it was already expired.Comment #92
fy1128 CreditAttribution: fy1128 commentedComment #93
ChristopheDG CreditAttribution: ChristopheDG commentedAny time estimate when this would be committed to the dev version?
I'm starting a new project where this module/feature would be an excellent fit...
Thank you for all the hard work everyone!
Comment #94
lubwn CreditAttribution: lubwn commentedI need the very same functionality but with unlimited licenses. Because I am using Commerce Subscription and it requires to set the license type to unlimited, since intervals are managed by subscriptions instead of license itself (as far as I understand).
Patch applied smoothly but did nothing to my installation, people still can not upgrade their subscription / renew license because since it is unlimited it just says they may not pursache the same license again.
Any solution to this? Thanks in advance!
Comment #95
arunkumarkI have resolved the issue by using the
hook_entity_insert()
in my Custom module. Below is the hook implementation.Comment #96
robcarrHas this still not made it to DEV? The patch at #91 won't apply (via Composer) to the current DEV release (11 Feb 21)
Comment #97
Kojo Unsui CreditAttribution: Kojo Unsui commented@robcarr, have you tried #86? So far it was applying smoothly to DEV.
Comment #98
joachim CreditAttribution: joachim at TrainingCloud commentedSame here.
Please don't do unrelated fixes in a patch. It makes it MUCH harder to review.
Even if they're necessary, do them in a separate issue that you can reference as needing to be committed first.
These fixes to tests were maybe committed recently on a separate issue and hence no longer apply?
If they are still needed, they should be moved to a separate issue anyway, as they add noise to this patch.
Comment #99
robcarrAlso tried the patch at #86 and that fails too 🤔
Comment #100
robcarrI've just tried to unpick the patches at #86 and #91, and manually apply the text changes (taken most of the day). The patch files are long (obviously lots of work) and many changes are just unrelated code formatting changes to the whole module - which is an unhelpful distraction to trying to solve this significant issue.
However, I lose track about half way through patch #91 with the changes in
src/LicenseAvailabilityCheckerExistingRights.php
with the new function introduced:How is
$unsetNotPurchasableMessage
dealt with?Also could not make sense of the patches on quite a few of the tests which have clearly changed a lot and most of the changes are already in DEV.
I haven't tested the patch particularly thoroughly, but it seems functional and should apply to the current DEV release (and prob alpha20 too). I'd be grateful if anyone with a bit more expertise could review it, especially the tests and the changes to
src/LicenseAvailabilityCheckerExistingRights.php
Comment #101
robcarrSorry last patch had loads of trailing white space errors so wouldn't apply. One attached to this post should apply and work, but if anyone can review the tests listed in the patch at #91, plus the
private function checkPurchasable()
function insrc/LicenseAvailabilityCheckerExistingRights.php
in this patch then that may push this issue closer to a solution.Comment #102
robcarrAlthough the patch at #101 applies locally, it won't apply via Composer and d.o. plus the test has come back with loads of errors. I'm probably a little out of my depth here... 🆘
Comment #103
robcarrComment #104
robcarrI'm making lots of mistakes (aka learning) here... attached is yet another patch file generated by a different tool and seems to include full path to module from webroot, so it may not apply to a vanilla D8/9 installation. My previous patches didn't include the 3 new pages created, so they're in this one.
Comment #105
robcarrThe patch at #104 (commerce-license-ability-to-re-purchase-a-license-to-extend-it-102.patch) just fails everywhere. I hope it's of some use to someone else who can take this forward.
Comment #106
joachim CreditAttribution: joachim at TrainingCloud commentedWell your first problem is that you've made the patch from the wrong place!
If your whole site codebase is in git, then do:
1. cd web/modules/contrib/commerce_license
2. git diff --relative -- .
That gets you a git diff of just the current folder, with the file paths in the diff relative to where you are.
Comment #107
robcarrThe other problem is that the code in my patches just don't work. Without having much knowledge of the Commerce framework, I tried my best to manually map across a lot of the code from the older patches; clearly my lack of expertise failed in this area.
The renewal functionality is really important to make this module more relevant to real world solutions. I'm also looking at more flexible ways to send expiry notifications (eg, 30 days before expiry), but such functionality depends on this issue being addressed.
Comment #108
robcarrLatest updated patch. Tried this out on a few scenarios, so appears functional. However, needs work on tests.
Comment #109
robcarrPrevious patch failed to include new files 🙄
Comment #110
robcarrAdded back trait LicenseOrderCompletionTestTrait (
/tests/src/Kernel/LicenseOrderCompletionTestTrait.php
) to try and get the patch through some tests. This trait was removed in alpha19 (probably for good reason), but causes a critical failure if not included.Comment #111
robcarrThe patch at #110 fails a few tests.
If a license is being renewed, but during checkout the item is deleted from the cart, then the whole user's license is deleted. This is current erroneous behaviour on this patch (tested manually too) and reason test fails.
Unsure of reasons for failures in 2-4, one of which being that the base table commerce_license doesn't exist...
Comment #112
joachim CreditAttribution: joachim as a volunteer commented> If a license is being renewed, but during checkout the item is deleted from the cart, then the whole user's license is deleted. This is current erroneous behaviour on this patch (tested manually too) and reason test fails.
This was a REALLY HARD corner case to figure out.
When the product is in the cart, we need to know whether it's first-time purchase or a renewal purchase, basically, because how to restore things when the user removes the item from the cart is completely different in those two cases.
Comment #113
robcarrNow need to focus on /src/EventSubscriber/LicenseRenewalCartEventSubscriber.php and add
public function onCartOrderItemRemove(CartOrderItemRemoveEvent $event)
. Thought this might work:I can't get that to trigger a
public function onCartOrderItemRemove(CartOrderItemRemoveEvent $event)
, so not sure where else to look to catch cart item deletions. 🤔Comment #114
junaidpvI improved #110.
CommerceOrderSyncRenewalTest
was failing because the "activate_on_place
" setting was not enabled so not extending the expiry. I added a line to enable "activate_on_place
" for the product variation which fixed it.Regarding removing the item from the cart. There is already
commerce_license_commerce_order_item_delete()
to delete the license unconditionally. I improved that part. If the license is in therenewal_in_progress
state, then it will first put the license in therenewal_cancelled
state then back to the active state. We can't put the license inactive
state directly because of the wayLicense::presave()
acts if license transits fromrenewal_in_progress
toactive
state by extending the expiry time.I could not fully solve failing of
CommerceAvailabilityExistingRightsTest
. Need to fix that.IMPORTANT: The patch is incompatible with the latest release, 8.x-2.0alpha21. It always create new license when attempting to renew the existing one. I think it is happening because of the change appeared in this commit.
Comment #115
junaidpvToday I found I was wrong with my statement yesterday. It works with 8.x-2.0-alpha21 too. Improved
CommerceAvailabilityExistingRightsTest
by adding to install the schema for commerce_license entity, which fixed the error happened #110.Comment #116
robcarrThank you so much for your work on the patch Junaid. I've tested it and it works really well, including the case if the license is removed from the cart. Fantastic progress.
Would be really helpful if someone else could review and double check.
+1 for RTBC
Comment #117
junaidpv@robcarr glad to hear you found it working.
However, we found it having a con which may require more changing.
We have a production having multiple variations allowing to purchase roles license for 1 year, 2 year and 3 year.
On further testing, we found it is not allowing to repurchase the role license with a different product variation of the same product.
For example, Let's assume I purchased 1 year membership before. Then I am only able to re-purchase 1 year member, not 2 or 3 year memberships!
@all Any inputs and or improvement on the patch are highly appreciated.
Comment #118
Drupal Centric CreditAttribution: Drupal Centric commentedThanks @junaidpv that's working fine for me too.
+1 for RTBC
Comment #119
robcarrThe only workaround for your patch is to let license expire, which would allow a license of any duration to be re-purchased. Not ideal, but would work. I'm tempted in my current project to only offer 12 month licenses to be purchased.
Comment #120
robcarrThe patch at #115 no longer works with latest DEV (6 Aug 21)
Comment #121
robcarrRe-roll attached. Seems to work, but needs a bit more testing.
Still has the issue where users can only renew for the same duration as currently held.
Comment #122
joachim CreditAttribution: joachim as a volunteer commented> Still has the issue where users can only renew for the same duration as currently held.
I really don't know how that can be changed.
The license is tied to a product variation, which sets the duration.
We'd have to rethink how the renewal works -- let the user pick a different PV, but still enforce that it has to be for the same grantable. Complex stuff.
Comment #123
robcarrI think we could quickly get into diminishing returns to allow users to renew a different license (Product Variation). It looks incredibly complex.
If the patch provides acceptable functionality (and a few others can review it), then this might be the most pragmatic way to allow users to renew, as it fulfils the majority of use cases. The caveats would be:
The 2nd caveat is the same functionality as offered by the Commerce License module anyway (ie, the license has to expire before renewal is possible), but this patch at least permits renewal prior to expiry in the majority of cases.
Comment #124
robcarrUpdated patch attached. Last one failed testing (see #3223616: unit tests fail using recent versions of commerce)
Comment #126
robcarrPatch #124 seems to be failing testing of Drupal\Tests\commerce_license\Kernel\CommerceOrderSyncRenewalTest::testRemovingProductFromCart at line 390:
Resulting in 2 errors:
With no experience of automated testing, I've spent a day scratching my head and getting nowhere with the test code. With a manual test of the 'failing' function (ie, deleting a renewing license from the basket) it works fine, so it seems there's something wrong with the test, rather the patched module code itself.
Comment #127
joachim CreditAttribution: joachim as a volunteer commentedThat looks REALLY hacky.
Comment #128
junaidpv@joachim Agree. But that was only way I found as said in #114.
Comment #129
SocialNicheGuru CreditAttribution: SocialNicheGuru commentedcaused major error:
Drupal\Core\Entity\EntityStorageException: $string ("You already have one item similar to @product-label in your cart.") must be a string. in Drupal\Core\Entity\Sql\SqlContentEntityStorage->save() (line 846 of drupal8.9/html/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php).
I upgrade the commerce_license
I goto a product page like product/3 and I get WSOD with the above in my logs
Comment #130
pmaguniaRe-roll from #115.
In PHPUnit 9, arraySubsetAsserts is removed. I added a dev package to composer.json which replaces that function.
@robcarr the version error in the test you were seeing was because the cart needed to be reloaded.
Is the inability to renew a license with a different variation a blocker?
EDIT: Didn't mean to add the same file 2x. They are identical.
Comment #131
pmaguniaAdded Role label.
Comment #132
pmaguniaNow with all Drupal tests.
Comment #133
robcarr@pmagunia thank you for your work on an updated patch. I'm currently working an a different project and I've had to park the project utilising Commerce/Commerce License, but will hopefully test again soon.
I don't believe that the the inability to renew a license with a different variation is a blocker. From a usability perspective, a user wishing to renew and shift to a different plan would just have to let the current plan (License) expire. I think the patch's implementation would suit the majority of cases. It would better to commit this patch to the module and raise a separate issue if this become significant to others.
Comment #134
joekersThanks for the updated patch pmagunia, I've given it a test and it seems to be working well!
I was getting the same issue as SocialNicheGuru with the previous patch I was using (#124) and also the latest patch.
Drupal\Core\Entity\EntityStorageException: $string ("You already have one item similar to @product-label in your cart.") must be a string. in Drupal\Core\Entity\Sql\SqlContentEntityStorage->save() (line 846 of drupal8.9/html/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php).
I found that it's caused by this line and is caused by $message already being TranslatableMarkup:
\Drupal::messenger()->addError(t($message));
I've updated it to this in the patch - it's the only change from #132:
\Drupal::messenger()->addError($message);
Comment #135
joekersI'm not sure if we should be covering the case where a user tries to add the same product to their cart when the license is already "renewal_in_progress". I tried this and got some undesired behaviour.
The product was added to the cart and the quantity was increased even though
LicenseMultiplesCartEventSubscriber
is supposed to force the quantity. There weren't any messages about the item being added to the cart either.The reason for this is due to the
LicenseRenewalCartEventSubscriber
trying to apply the "renewal_in_progress" transition again to the license. This causes an exception in the Renderer and skips the other event subscribers:The transition "renewal_in_progress" is currently not allowed. (Current state: "renewal_in_progress".)
Therefore I've added a check to make sure that the license isn't already in the state before applying it. This allows the process to continue as normal. I've done some manual testing and this seems to be working well.
Comment #136
joekersOne loophole that I've found is that if the user is logged out, adds the product to the basket, and then logs in within the checkout flow. This creates a new basket and the event subscriber checks are bypassed.
I'm not sure if this is the right approach, but is there any way we could check the contents of the basket when it's assigned to the user on login?
Comment #137
robotjox CreditAttribution: robotjox commentedPatch fails to apply for me on Drupal 9.3.6
Comment #138
robotjox CreditAttribution: robotjox commentedRerolling as it did not apply on 9.3.6.
Comment #139
junaidpv@robotjox, It appears, you added previous patch file in your patch :) Please correct it. Thanks!
Comment #140
robotjox CreditAttribution: robotjox commentedoops, sorry about that! Will try to get a new patch up today
Comment #141
robotjox CreditAttribution: robotjox commentedHope this works better...
Comment #142
joekersAnother thing I've noticed is that the license is revoked when the license is being renewed. I don't think this is desirable as if the user decides to leave the checkout without renewing, then their license will remain revoked.
I think we just need to update the condition around when a license is being changed from the 'active' state to also check the renewal state before revoking the license. I can update the patch when I get a chance.
Comment #143
robcarrWe'd looked at that in #114. It's an important scenario to catch, as abandoned carts are quite a frequent occurrence. Thanks for all your work @joekers
Comment #144
attisanThe changes to the
LicenseOrderProcessorMultiples
result in breaking carts with multiple products with (potentially different) licenses being added to a cart - so only a single product with a license can be purchased at a time. This specifically breaks the usage of pado or vado where multiple licenses (e.g. roles) should be bought together.Comment #146
attisanMoved the latest patch (#141) to the issue fork, hid the tremendous amount of patch files and changed
LicenseOrderProcessorMultiples
to allow multiple different licenses in a single order.Took a look at the test issues and figured to leave it be as long as they seem to stem from the dev branch.
Would be great if we could continue working on the issue fork (with the open MR). Patch can be applied just like it is possible with patch files using the plain diff link.
Comment #147
junaidpvApplies fixes for:
@joekers, @robcarr . Please have a look and try to see if it works for you as well.
Comment #148
junaidpvAs already stated earlier, it is not possible to repurchase a different variation. Fixing that will require a major rewrite of the module.
We wanted to fix that in our project. I prepared a patch specifically for our project. We have only three PVs granting role for 1, 2 and 3 years. This hack helped to work with them. I guess, it wont work if you are having more than one type of PVs and more attributes.
Please replace PV bundle name in this patch "our_project_membership_pv" with the corresponding PV bundle name in your project.
Looking forward to hear if anybody found this helpful and feedback.
Comment #149
jsacksick CreditAttribution: jsacksick at Centarro commentedComment #150
rszrama CreditAttribution: rszrama at Centarro commentedComment #152
TomTech CreditAttribution: TomTech at Centarro commentedThis (large) patch/MR (with some modifications) has been committed.
The primary additional changes are:
renewal_in_progress
should be checked alongsideactive
, e.g. during the deletion of a license or when cron is processing expiring licensesComment #155
Morbus IffThis patch appears to be partly responsible for critical error in 3.0-rc1: https://www.drupal.org/project/commerce_license/issues/3362663