Spun out from #2844920: Allow customer profiles to be reused.
Right now orders have a profile reference and payment methods have a profile reference. The field used is entity_reference_revisions, which stores both an ID and a revision ID. The revision logic is currently unfinished.
Problems:
1) Deleting a user deletes their profiles, which makes already-placed orders incomplete.
2) Deleting a profile from the addressbook also makes already-placed orders incomplete.
3) When reusing profiles it is possible to select the same profile twice, once for billing once for shipping.
It is then possible to try and edit both profiles, creating a conflict.
4) Revisions have proven to be complex to reason about, leading to possible future bugs.
Solution:
- Orders and payment methods (and shipments) always have their own profiles, with the uid of 0.
These profiles are owned by the parent entity, and don't show up in the addressbook.
This protects them from deletion, and allows them to be edited without influencing other
orders/payment methods/shipments.
- Addressbook profiles have a normal uid matching the current user.
- Reusing a profile now becomes about selecting an addressbook profile and copying its
values to the actual profile, which is always freshly created.
- When editing reused profile information the addressbook profile is not automatically updated.
Instead, we make it an explicit operation (a checkbox that can be on/off by default), which copies back
the updated values to the addressbook profile.
| Comment | File | Size | Author |
|---|---|---|---|
| #46 | 3022850-46-addressbook-part1.patch | 69 KB | bojanz |
| #45 | 3022850-45-addressbook-part1.patch | 69.04 KB | bojanz |
| #44 | 3022850-44-addressbook-part1.patch | 69.04 KB | bojanz |
| #39 | steps.png | 7.53 KB | mindaugasd |
| #30 | profile-fields.png | 19.96 KB | mindaugasd |
Comments
Comment #2
jons commentedSounds good!
We are developing a usecase where we want to limit certain users to only be able to use specific addresses for billing and shipping (a B2B scenario), and not allow them to edit those addresses.
We currently control the addresses in their profile and limit access via hook_ENTITY_TYPE_access().
The proposed solution will work well for them.
Will the reused-address edit & copy-back box processing respect Profile access or be controllable some other way?
Will the source Profile address dropdown content be easily controllable (if it needs it to be)?
thanks
Comment #3
ericchew commentedI have a B2B type scenario that I plan on implementing too. I am going to create a Company entity that users can be linked to. A Company will store its own billing / shipping profiles. When a customer (or employee on the admin side) goes to create an order, I'd like to be able to show Company addresses in the profile widget (if user belongs to a Company). User's can have their own personal addresses as well (normal commerce functionality).
The scenario from #2 would also be applicable for this too, because we may want to restrict who can edit their company addresses.
The big reason for doing this is because it isn't terribly uncommon for companies to change addresses. If we were to just copy the company profiles to each user that belongs to the company, we'd end up with a lot of stale profiles if the company changes addresses.
I'm hoping this scenario will be something I can achieve with the upcoming changes to profile.
Comment #4
mglamanReviewing and taking a stab at this.
Comment #5
mglamanCreated #3039163: Create a "data" field using a map data type..
For updating the "source" profile. We somehow need to keep a reference to the original profile entity. We need to introduce a
$profile->datafield. The source ID can stick there to allow updating the addressbook reference. There is also a need for$profile->datato hold a flag for when converting profiles after anonymous checkout.Comment #6
mglamanHere is a starter patch. It abstracts the three times we retrieve a billing profile and push it into an InlineForm. For now, \Drupal\commerce_order\CustomerBillingProfileTrait::ensureParentProfileCopy provides the logic that:
* Takes billing profile value passed from the "parent" entity (order, payment method)
* If it is not null, return that value. It has a billing profile it owns.
* If it is null, load the user's default profile, If there is not a default, create a stub entity
* Create a duplicate of the existing profile entity and change its owner to anonymous.
The existing tests seem to pass with this, which they should. The next step is to make more assertions specifically around billing profiles.
Comment #7
mglamanI found a bug in Address which prevents the default address widget from populating, because the cloned profile is technically new: #2838457: Re-enable the default value functionality for Address fields.
This is a patch which provides the start of a basic test. The address never has a value due to this bug.
Comment #8
mglamanComment #9
mglamanThis is officially blocked on the Address issue for a default value. Here is an updated patch where the problem was resolved locally. Test passes and profile is reused, data is exposed in the form.
Comment #10
mglamanTalked w/ Bojan and I got a bit ahead of myself. The above focuses more on reuse versus ownership alone. There is still a blocker here, on Profile's
datafield. New patches incoming.Comment #11
mglamanHere is an initial patch which ensures all billing profiles generated are set to
uid = 0.Checkout panes generate profile stubs belonging to
uid = 0instead of the order or payment method's owner. Orders and payment methods ensure the profile owner isuid = 0.That leaves the next steps to be:
1) Checkbox in CustomerProfile that says "Add this address to my address book", sets $profile->data flag
2) Code that detects the flag and creates a duplicate of the order billing profile, then assigns it to the user
For #2, that can be an order placed subscriber for checkout panes. For payment method add forms outside of checkout... TBD.
Comment #13
mglamanPATCH WARNING: This adds hook_base_field_info to add a data field to profile for testing. This is so that we can write tests that pass until #3039163: Create a "data" field using a map data type. lands.
This adds a checkbox which allows saving an address entered at checkout to the user's addressbook. There is an event subscriber that creates a copy of the order's billing profile to the user's address book when flagged.
---
There seems to be a legitimate regression here, though.
The test code:
Submitting the coupon pane is causing the entire form to submit and change the billing information.
Comment #14
mglamanThis needs to check if the profile has been flagged or not and set it as a default value.
Comment #15
mglamanAdds the checkbox.
Comment #18
mglamanPicking this back up today.
Comment #19
mglamanFixes a logic check which should resolve most failures (ie: order does not have a billing profile attached.)
A quick rundown of follow-ups for the next patch:
- Add a Kernel test against BillingProfileConvertSubscriber
- Ensure added Functional* tests assert the checkbox works during checkout to convert billing profiles
Comment #21
mglamanAdds kernel tests for:
- Add a Kernel test against BillingProfileConvertSubscriber.Comment #22
mglamanLint fix, clean up functional tests.
Comment #24
mglamanThis strict test is causing the copy test failures. It is not being saved as a strict boolean value.
Comment #25
mglamanThis should bring us back to just the failure for CouponRedemptionPaneTest::testCheckoutSubmit where submitting a coupon causes the profile field values to submit as well, when they previously did not.
Comment #27
mglamanThis marks
\Drupal\Tests\commerce_promotion\FunctionalJavascript\CouponRedemptionPaneTest::testCheckoutSubmitas skipped, for now.It also fixes a bug where the "Add to addressbook" was always selected due to `__isset` call.
Comment #28
mglamanHanding over to bojanz for review.
Comment #29
bojanz commentedThe address default value bug has been fixed, a new release has been tagged, and Commerce 2.13 requires it.
I've also committed the data field issue for Profile.
New patch incoming.
Comment #30
mindaugasd commentedOur site needs different fields for company and person like this:
I would guess it may be possible then shipping/billing profiles are separated in combination with "Conditional fields" module.
Comment #31
mindaugasd commentedI struggle to understand current state of profiles and how I am supposed to make it work. Hopefully, profiles will improve some day.
The most complex part is why shipping and billing is the same profile by design, then information on both have to be totally different depending on various situations.
Comment #32
jons commentedNote on testing: ensure you get latest -dev of Profile module as well, as indicated in #29.
Is a new Commerce patch imminent or is this one pretty stable?
Comment #33
chrisolofWould it also make sense to move to a standard entity reference field? It seems that the solution outlined in the issue description would no longer need entity_reference_revisions...
I ask because using a standard reference field would simplify the setup and drop a dependency on something we don't appear to be utilizing anymore (and make writing order & order profile migrations simpler).
Comment #34
bojanz commentedRetitling for clarity. A new patch is coming within the next 24h.
It also includes initial addressbook functionality (fields are prefilled with the default profile data).
Committed a few more issues to Profile -dev to decrease size:
#3052889: Add unsetData() and populateFromProfile() methods to Profile
#3052254: Clean up profile types
Prepared the issue for the next step: #2910193: Allow reusing profile values from another inline form ("Billing same as shipping").
Removing that code from this patch to land it quicker.
Some people want the same profile type, some people don't. It's use case specific. Our plan is to support both, and document when each option makes sense. This issue is the first step towards that.
Yes, once the full set of issues is implemented, we will be able to switch to an ordinary entity reference field. That will need to wait until the release after the next one though, because we need to require Drupal 8.7.0 for it to work well (Drupal 8.7 fixed a bunch of hard update problems).
Comment #35
agoradesign commentedand why can't we require Drupal 8.7 already now?
Comment #36
bojanz commentedBecause I want to have one last 8.6 and PHP5 compatible release (2.14).
Many will want to update Commerce for the addressbook functionality, but might not be ready to update to 8.7 yet, especially since 8.7.0 seems to have some upgrade bugs.
Comment #37
mindaugasd commentedGlad to hear this. With this you will cover my country, since every shop has two profiles here. And it makes total sense, don't understand the other possibility. Maybe laws, local traditions or what not different, don't yet know why single is ok in other places. Looking forward for that documentation.
Comment #38
bojanz commentedYou still have two profiles, they just have the same fields (same bundle).
Many sites have only an address field and a phone field, so they don't need separate bundles for billing and shipping.
You'd put "delivery instructions" on the shipment itself, so they're not profile material. Some sites have a "tax/vat number" field, but that can be done with separate form modes (hide the field on the shipping form mode, show it on the billing form mode).
Comment #39
mindaugasd commentedIts two profiles, because payment/shipping is in two steps. And those are two steps, because we have gazillion payment and shipping options. Maybe small countries have a lot of options, and big countries/international can only use few universal options, so they can fit those options on a single page. But for us it becomes very natural to have two profiles, because shipping and payment becomes very diverged.
And another big lack of commerce becomes again lack of those shipping and payment options. But I hope Commerce will get better and become more popular so those payment/shipping providers will create modules in some time.
Its ok, as long as it can be done.
Comment #40
mindaugasd commentedYes, I already have those fields on shipment. But shipment itself is lacking a bit since shipping methods diverge too as I described here #3044641: Allow the shipping method to determine shipment type
Comment #41
bojanz commentedNone of this is affected by any of the addressbook patches. You are free to create separate checkout steps for billing and shipping if you wish to do so.
Shipping is a contrib module, its features depend on community investment (your own patches and money). Discussing its general shortcomings is out of scope for this issue.
Comment #42
mindaugasd commentedSorry, I write out of topic. The thing I understand now - it will be possible to have different fields on billing and shipping profile. That makes everything else mostly possible. Thanks.
Comment #43
Gode.Agarunov commented@bojanz
Is the patch you are dropping going to be compatible with 8.7? I updated to 8.7 a few days ago and did have problems because with the DB schema updates making menu link content revisionable because I had a custom image field on my menu content, but that's taken care of now thanks to the work of the community, I would rather just stay at 8.7 at this point, but having registered users not have to type in their address over and over is critical, so I may have to roll back if there is no other way.
Thanks for your work.
Comment #44
bojanz commentedLet's see what the testbot thinks. I'll update the comment with a detailed summary if green.
This requires Profile -dev, of course.
Comment #45
bojanz commentedThis one is ready for review! It has been bikeshedded and iterated upon, and should be mostly final.
It fixes the ownership for both order and payment method profiles.
It has an update hook that will update all customer profiles to uid 0, emptying the existing address books. This is necessary to ensure data integrity, and to avoid duplicates.
UI-wise, we pre-fill the profile form with the default profile information. Below is a checkbox labelled either "Save to my address book" or "Update my stored address", depending on whether the profile type supports multiple profiles. The more complete UI will be implemented in #3053165: [Addressbook part 2] Complete the UI by allowing choice between multiple addressbook profiles.
Note that the form is not pre-filled if the form is rendered after an ajax operation (if you click on "Credit card" at checkout for example). This is noted in the tests, and caused by #3047340: The address default value isn't always shown when ajax is used. Currently looking into it.
Also note that if you're using Shipping, it will need #3054351: Add support for the Commerce 2.14 address book.
Comment #46
bojanz commentedFixed #3047340: The address default value isn't always shown when ajax is used in Address, here's a reroll that makes Commerce use Address -dev, and uncomments the previously-commented test assertions.
Comment #48
bojanz commentedCommitted, so that we can proceed to implementing and testing parts 2 and 3.
Please test, and report feedback in #3053165: [Addressbook part 2] Complete the UI by allowing choice between multiple addressbook profiles.
Comment #49
bojanz commentedUpdating credits to add Liip.
Liip is sponsoring the completion of the address book functionality. Thanks, Liip!