Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple Vendors with Single Set of Products #12

Closed
idawgik opened this issue Dec 16, 2020 · 10 comments · Fixed by #13
Closed

Multiple Vendors with Single Set of Products #12

idawgik opened this issue Dec 16, 2020 · 10 comments · Fixed by #13

Comments

@idawgik
Copy link

idawgik commented Dec 16, 2020

This is more a pre-sales/howto question.

I have a possible project which would have multiple vendors but only one set of products with Craft Commerce.

Is it possible with the vendor/stripe connect integration to tell which vendor an order should be applied to when only using one set of products? We would prefer not to have to duplicate each of the products every time a new vendor is added. Customers would only be ordering from one vendor at a time.

Thank you for your help.

@kennethormandy
Copy link
Owner

Hi @idawgik,

If I’m understanding correctly, it sounds like what you’re after is already possible, or should only require some minor changes.

I typically set the up the Payee field on the Craft Commerce Product, but you should be able to get the result you’re after by setting the Payee field on the Product’s Variants:

Screen Shot 2020-12-17 at 10 03 26 AM

Then, you’d fill in a different Payee (vendor) for different Variants:

Screen Shot 2020-12-17 at 9 58 18 AM

In my example, upon checking out, when someone purchases SKU PBS-001 for $30, the Payee will be Jane. When someone purchases SKU PBS-002 for $15, the Payee will be me.

Let me know if that will work for you, and what you have in mind for presenting the products on the front-end.

@idawgik
Copy link
Author

idawgik commented Dec 17, 2020

Hello,

Well, what we want to do is allow each vendor to have their own storefront of sorts on the site, but just have a single set of products so we don't have to duplicate/import each time we add a new vendor if possible if that makes sense.

@kennethormandy
Copy link
Owner

That makes sense. The plugin might require some small changes or a hook alongside it to support that without duplicating products. Otherwise there’s nothing major that should prevent this from working as you’ve described it.

Do you have a specific plan for how one storefront is differentiated from another? Ex. is it based on the URL?

The plugin will need different way to know what Payee should be paid, if it isn’t via the Product (or Variant on the Product).

In Twig, when someone adds a product to their cart, maybe you’d set an option that indicates what user should be paid?

{# Maybe this would actually be set based on the URL for their storefront? #}
{% set userId = 1 %}

{# Whatever your usual Add to Cart form is… #}
<form method="POST">

  {# …but with an option that adds the userId for the vendor who is
   # going to be paid for the order, so we know who they are later. #}
  <input type="hidden" name="options[myCustomVendorId]" value="{{ userId }}" />

  <input type="hidden" name="action" value="commerce/cart/update-cart">
  <input type="hidden" name="qty" value="1">
  {{ csrfInput() }}
  <input type="hidden" name="purchasableId" value="{{ purchasableId }}">
  <input type="submit" value="Buy" />
</form>

Many sites wouldn’t want this to come from the front-end, which is one reason why it doesn’t work this way by default. But since all Users sell the same Products in your case, I could see this working for you.

Then, you’d have a small module that tells Marketplace to use that user ID, since there is no Payee set on the Product they just added to their Order:

// This is an idea, and isn’t actually supported within the plugin yet.
use Craft;
use yii\base\Event;
use kennethormandy\marketplace\events\PayeeEvent;
use kennethormandy\marketplace\services\Payee;

// …

Event::on(
    Payee::class,
    Payee::EVENT_DETERMINE_PAYEE,
    function (PayeeEvent $event) {
        Craft::info("Handle EVENT_DETERMINE_PAYEE event here", __METHOD__);

        $order = $event->order;
        $snapshot = $order->lineItems[0]->snapshot;
        $options = $snapshot['options'];
        $myCustomVendorId = options['myCustomVendorId']
        
        // Give the userId from the Product options, rather than using the
        // default Payee as determined by the plugin.
        return myCustomVendorId;
    }
);

Would something like that handle your use case? Everything else about the plugin would work the same, but you’d basically be gaining the option of setting the Payee via Twig, which I think most sites would want to avoid.

@kennethormandy
Copy link
Owner

kennethormandy commented Dec 27, 2020

Hi, I have this available for you to try out if you’d like.

I’m planning on releasing it as v1.1.0, but you can try it out now by requiring the branch if you’d like. Edit: this is now available in the stable version of the plugin.

After installing the plugin, add a new module to your Craft site, ex. modules/CustomPayeeModule.php:

<?php
namespace modules;

use Craft;
use yii\base\Module;
use yii\base\Event;
use craft\elements\User;
use kennethormandy\marketplace\services\PayeesService;

class CustomPayeeModule extends Module
{
    public function init()
    {
        // The usual
        
        // Set a @modules alias pointed to the modules/ directory
        Craft::setAlias('@modules', __DIR__);

        // Set the controllerNamespace based on whether this is a console or web request
        if (Craft::$app->getRequest()->getIsConsoleRequest()) {
            $this->controllerNamespace = 'modules\\console\\controllers';
        } else {
            $this->controllerNamespace = 'modules\\controllers';
        }

        parent::init();
        
        // Custom part starts here

        Event::on(
            PayeesService::class,
            PayeesService::EVENT_AFTER_DETERMINE_PAYEE,
            function (Event $event) {
                Craft::info("Handle EVENT_AFTER_DETERMINE_PAYEE event here", __METHOD__);

                // The $event gives you access to:
                // The Line Item: `$event->lineItem`
                // The User’s Stripe Account ID: `$event->gatewayAccountId`
                // Currently, the `$event->gatewayAccountId` will be set to the
                // ID for the User set on the Product, or will be null if there
                // is none. We want to change it to the User that was submitted
                // from the dropdown.

                $lineItem = $event->lineItem;

                // Using Craft Commerce Lite, so there’s only one
                // Line Item anyway
                $snapshot = $lineItem->snapshot;

                // The Options on the Line Item, which includes
                // the ID of the User we want to pay from the dropdown.
                $options = $snapshot['options'];
                $userToPayId = $options['myUserToPayId'];

                Craft::info("[CustomPayeeModule] " . $userToPayId, __METHOD__);

                if (!isset($userToPayId)) {
                  return;
                }

                // Find the User
                $userToPay = User::find()
                    ->id($userToPayId)
                    ->one();

                Craft::info("[CustomPayeeModule] [User] " . $userToPay, __METHOD__);

                // The name you gave your Connect button field, so we can pull
                // it from the User you actually want to pay. I used
                // “platformConnectButton” like in the README
                $connectButtonFieldName = 'platformConnectButton';
                $userToPayAccountId = $userToPay[$connectButtonFieldName];
                
                // Modify the User that was selected via the checkout options (or
                // however else you want to set up the UI), rather than using the
                // default Payee that would typically be set on the Commerce Product.
                $event->gatewayAccountId = $userToPayAccountId;
            }
        );
        
    }
}

In app.php, load your new module, as per usual:

return [
    'modules' => [
        'my-module' => \modules\Module::class,
        'custom-payee-module' => \modules\CustomPayeeModule::class,
    ],
    'bootstrap' => [
      'custom-payee-module',
    ],
];

That is the extent of the custom code, although I’ll keep thinking on a clean way I could support your use case without the custom module at all in the future. In the meantime, the above example will already work.

Now, what you’ll want to do is modify the actual Twig templates, so you can add the myUserToPayId option to the Line Item. For example, on a Product’s Add to Cart form:

{# The name you gave your Marketplace Connect Button field #}
{% set connectButtonFieldName = 'platformConnectButton' %}

<select name="options[myUserToPayId]">
  {% for user in craft.users.all() %}
    {% if user[connectButtonFieldName] is defined and user[connectButtonFieldName] %}
      <option value="{{ user.id }}">{{ user }}</option>
    {% endif %}
  {% endfor %}
<select>

Using the example templates, that would give you something like:

Screen Shot 2020-12-27 at 12 52 37 PM

Of course, you could also make it a hidden input and change the option based on something like the route instead, depending on what you have in mind for your platform.

Let me know how this would work for you!

@idawgik
Copy link
Author

idawgik commented Dec 29, 2020

Hey thanks for your work on this! It does sound like this would be a good solution for our use case.

I am still waiting on the client to approve the work, so don't have an environment yet that I can test everything in, but will keep you updated.

Oh just noticed this would be a custom module instead of a plugin?

@kennethormandy
Copy link
Owner

Great! The small custom module would be in addition to the plugin. It uses an EVENT_AFTER_DETERMINE_PAYEE event provided by the Marketplace plugin, so you do need both.

That event lets you dynamically change the Payee on any Product, rather than manually setting the Payee on the Product in the Craft Commerce admin area.

The example I gave is a complete example, but it also gives you the flexibility to modify that logic further if you need to, ex. only for this type of product or only for this user group or something.

I’ll get this merged and published as a new version, but feel free to ask here or email me if you have any other questions.

@kennethormandy
Copy link
Owner

This is supported and published in v1.1.0 now! Let me know if you have any further questions about it.

@idawgik
Copy link
Author

idawgik commented Apr 25, 2022

Hello, I wanted to thank you again for this. It's been working out great for the use case of this client (which I'm now finally almost complete with development for!).

One piece I would like to adjust though are the orders displayed on a user's admin profile with the MarketplaceConnectButton field. For my use case, I added a custom field to the orders which holds the user that the purchases are for (this was separate from the module, which is used for all of that functionality).

I would like to update the template for your field to only show orders where that custom field matches the user, instead of what you have now which looks at the payee field on the line items (which doesn't match in my use case).

Is there an easy way to override the template for this? I would rather not edit your source files directly.

@kennethormandy
Copy link
Owner

Hi @idawgik, glad to hear it’s working out well for you.

Feel free to open a new issue for this, it sounds like it is somewhat separate?

For my use case, I added a custom field to the orders which holds the user that the purchases are for (this was separate from the module, which is used for all of that functionality).

I’m not sure I follow this part—you mean you’ve added a custom field of your own, separate from Marketplace and the module we talked about, and that gets filled in with the same user that is used as the payee?

The payment splitting calculations intentionally all happens at the line item level, even in Marketplace Lite. So even in your case, where there are multiple line items all with the same payee, I would have still expected that template to show the same thing. In other words, isn’t the user you are setting on the order, the same one that Marketplace is the payee on the line items in the order? Let me know if I am misunderstanding something there.

Longer-term, I’d like to get rid of this list of orders that show up along with that field, as it makes too many assumptions. I think a dedicated area that lets you see or filter the Commerce orders view by payee might work better.

@idawgik
Copy link
Author

idawgik commented Apr 26, 2022

Yeah I can make a new issue for this.

Sorry I think I wasn't too clear what was happening.

On the products, I have the payee field, but not assigning that to a user (since ALL purchases on this site share products).

The order list with the field shows any orders where the payee field on the product matches what's on the line item, so nothing is returned (alternatively, when I did have the payee field field filled out on the product, all orders showed for that user, regardless of what they were assigned in the order).

Regarding me adding a new custom field, I did that at the order level, and it's just so in the main commerce order grid I can have a field showing who the vendor was. That's purely for the client's ease of use.

What I wanted to do was update the twig template for the field, but I'm not sure how to properly override that without just changing the source file, which of course wouldn't be advisable. I just need to change the query for that order grid. Right now the order grid template specifically looks for the payee field on the product matching the line item, which isn't the case for this site since we're assigning the payee upon add to cart from a shared set of products.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants