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.

Comments

bojanz created an issue. See original summary.

jons’s picture

Sounds 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

ericchew’s picture

I 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.

mglaman’s picture

Assigned: Unassigned » mglaman

Reviewing and taking a stab at this.

mglaman’s picture

Created #3039163: Create a "data" field using a map data type..

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

For updating the "source" profile. We somehow need to keep a reference to the original profile entity. We need to introduce a $profile->data field. The source ID can stick there to allow updating the addressbook reference. There is also a need for $profile->data to hold a flag for when converting profiles after anonymous checkout.

mglaman’s picture

StatusFileSize
new8.57 KB

Here 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.

mglaman’s picture

I 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.

mglaman’s picture

mglaman’s picture

Title: Rework the ownership model for customer profiles » [PP-1] Rework the ownership model for customer profiles
StatusFileSize
new13.6 KB

This 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.

mglaman’s picture

Talked 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 data field. New patches incoming.

mglaman’s picture

Status: Active » Needs review
StatusFileSize
new21.1 KB

Here is an initial patch which ensures all billing profiles generated are set to uid = 0.

Checkout panes generate profile stubs belonging to uid = 0 instead of the order or payment method's owner. Orders and payment methods ensure the profile owner is uid = 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.

Status: Needs review » Needs work

The last submitted patch, 11: 3022850-11.patch, failed testing. View results

mglaman’s picture

Assigned: mglaman » Unassigned
Status: Needs work » Needs review
StatusFileSize
new36.77 KB

PATCH 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.

1) Drupal\Tests\commerce_promotion\FunctionalJavascript\CouponRedemptionPaneTest::testCheckoutSubmit
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'John'
+'Johnny'

The test code:

    // Go back and edit the billing information, but don't submit it.
    $this->getSession()->getPage()->clickLink('Go back');
    $address_prefix = 'payment_information[billing_information][address][0][address]';
    $this->getSession()->getPage()->fillField($address_prefix . '[given_name]', 'John');
    $this->getSession()->getPage()->fillField($address_prefix . '[family_name]', 'Smith');
....
    $page->fillField('Coupon code', $coupon->getCode());
    $page->pressButton('Apply coupon');
    $this->waitForAjaxToFinish();
...
    $page = $this->getSession()->getPage();
    $given_name_field = $page->findField('payment_information[billing_information][address][0][address][given_name]');
    $family_name_field = $page->findField('payment_information[billing_information][address][0][address][family_name]');
    $this->assertEquals($given_name_field->getValue(), 'Johnny');
    $this->assertEquals($family_name_field->getValue(), 'Appleseed');

Submitting the coupon pane is causing the entire form to submit and change the billing information.

mglaman’s picture

+++ b/modules/order/src/Plugin/Commerce/InlineForm/CustomerProfile.php
@@ -92,6 +92,10 @@ class CustomerProfile extends EntityInlineFormBase {
+    $inline_form['add_to_addressbook'] = [
+      '#type' => 'checkbox',
+      '#title' => $this->t('Add this address to my address book'),
+    ];

This needs to check if the profile has been flagged or not and set it as a default value.

mglaman’s picture

StatusFileSize
new36.86 KB

Adds the checkbox.

The last submitted patch, 13: 3022850-13.patch, failed testing. View results

Status: Needs review » Needs work

The last submitted patch, 15: 3022850-15.patch, failed testing. View results

mglaman’s picture

Assigned: Unassigned » mglaman

Picking this back up today.

mglaman’s picture

Status: Needs work » Needs review
StatusFileSize
new36.88 KB

Fixes 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

Status: Needs review » Needs work

The last submitted patch, 19: 3022850-19.patch, failed testing. View results

mglaman’s picture

Status: Needs work » Needs review
StatusFileSize
new43.39 KB

Adds kernel tests for: - Add a Kernel test against BillingProfileConvertSubscriber.

mglaman’s picture

StatusFileSize
new39.71 KB

Lint fix, clean up functional tests.

Status: Needs review » Needs work

The last submitted patch, 22: 3022850-22.patch, failed testing. View results

mglaman’s picture

+++ b/modules/order/src/EventSubscriber/BillingProfileConvertSubscriber.php
@@ -0,0 +1,115 @@
+    $data = $billing_profile->get('data')->first()->getValue();
+    return isset($data['add_to_addressbook']) && $data['add_to_addressbook'] === TRUE;

This strict test is causing the copy test failures. It is not being saved as a strict boolean value.

mglaman’s picture

Status: Needs work » Needs review
StatusFileSize
new39.67 KB

This 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.

Status: Needs review » Needs work

The last submitted patch, 25: 3022850-25.patch, failed testing. View results

mglaman’s picture

Status: Needs work » Needs review
StatusFileSize
new40.6 KB

This marks \Drupal\Tests\commerce_promotion\FunctionalJavascript\CouponRedemptionPaneTest::testCheckoutSubmit as skipped, for now.

It also fixes a bug where the "Add to addressbook" was always selected due to `__isset` call.

mglaman’s picture

Assigned: mglaman » bojanz

Handing over to bojanz for review.

bojanz’s picture

Title: [PP-1] Rework the ownership model for customer profiles » Rework the ownership model for customer profiles

The 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.

mindaugasd’s picture

StatusFileSize
new19.96 KB

Our site needs different fields for company and person like this:

Profile fields

I would guess it may be possible then shipping/billing profiles are separated in combination with "Conditional fields" module.

mindaugasd’s picture

I 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.

jons’s picture

Note 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?

chrisolof’s picture

Would 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).

bojanz’s picture

Title: Rework the ownership model for customer profiles » [Addressbook, part 1] Rework the ownership model for customer profiles

Retitling 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.

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.

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.

Would 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...

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).

agoradesign’s picture

and why can't we require Drupal 8.7 already now?

bojanz’s picture

Because 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.

mindaugasd’s picture

Our plan is to support both, and document when each option makes sense.

Glad 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.

bojanz’s picture

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.

You 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).

mindaugasd’s picture

StatusFileSize
new7.53 KB

Its 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.

Checkout steps

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.

can be done with separate form modes

Its ok, as long as it can be done.

mindaugasd’s picture

You'd put "delivery instructions" on the shipment itself, so they're not profile material.

Yes, 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

bojanz’s picture

Its 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.

None 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.

But shipment itself is lacking a bit since shipping methods diverge too as I described here #3044641: Associate shipment type with shipping method?

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.

mindaugasd’s picture

Sorry, 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.

Gode.Agarunov’s picture

@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.

bojanz’s picture

Assigned: bojanz » Unassigned
StatusFileSize
new69.04 KB

Let's see what the testbot thinks. I'll update the comment with a detailed summary if green.

This requires Profile -dev, of course.

bojanz’s picture

StatusFileSize
new69.04 KB

This 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.

bojanz’s picture

StatusFileSize
new69 KB

Fixed #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.

  • bojanz committed 46d4142 on 8.x-2.x
    Issue #3022850 by mglaman, bojanz: [Addressbook, part 1] Rework the...
bojanz’s picture

Status: Needs review » Fixed

Committed, 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.

bojanz’s picture

Updating credits to add Liip.

Liip is sponsoring the completion of the address book functionality. Thanks, Liip!

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.