Skip to content
This repository has been archived by the owner on Feb 2, 2022. It is now read-only.

Discussion - Adding preprocessors to the first payment #80

Open
RobertBoes opened this issue Oct 4, 2019 · 16 comments
Open

Discussion - Adding preprocessors to the first payment #80

RobertBoes opened this issue Oct 4, 2019 · 16 comments

Comments

@RobertBoes
Copy link
Contributor

Preprocessors are currently not applied to the first payment. What I'm trying to do is adding extra items to the subscription, as a very basic test I've created the following preprocessor:

class Addon extends BaseOrderItemPreprocessor
{

    /**
     * @param \Laravel\Cashier\Order\OrderItemCollection $items
     * @return \Laravel\Cashier\Order\OrderItemCollection
     */
    public function handle(OrderItemCollection $items)
    {
        $addon = OrderItem::make([
            'process_at' => now(),
            'owner_type' => $items->first()->owner_type,
            'owner_id' => $items->first()->owner_id,
            'description' => 'Extra items',
            'currency' => 'EUR',
            'tax_percentage' => 0,
            'quantity' => 2,
            'unit_price' => 1500,
        ]);

        return $items->push($addon);
    }
}

This does add two items to subsequent payments, but not to the first payment.

After reading through the code, I'm want to try to create a PR, but I'm not sure if I'm on the right track. It seems like the coupons and PersistOrderItems are handled differently on the first payment, basically they're added "manually".

My idea is to add a property to ProcessCoupons and PersistOrderItems to indicate that those two preprocessors should not be handled on the first payment. Then in the create method of the FirstPaymentSubscriptionBuilder I can fetch all the preprocessors, filter out those which shouldn't be applied and then add them to the $actions array.

Now my question is, would this work and would this be the correct way to handle preprocessors on the first payment?

@sandervanhooft
Copy link
Collaborator

Just to understand, can you give me a use case for using a preprocessor on the first payment?

@RobertBoes
Copy link
Contributor Author

We have a few base plans, like regular and pro for example. But we also have some more dynamic "add-ons" for those plans. For example a user has a regular plan which includes x items, but they can purchase additional items for that plan (also monthly billed). In the code sample above you can see I'm adding 2 OrderItems to the plan. So an invoice would look like this:

Description Units Unit price Total
Regular subscription 1 € 10.00 € 10.00
Extra items 2 € 2.50 € 5.00

Another option would be to add a separate subscription for the add-ons, but that can cause the subscriptions to easily go out of sync. For example when the user decides to buy the add-ons a few days after they have purchased the main subscription.

@sandervanhooft
Copy link
Collaborator

Thanks for clearing that up. I understand the requirement now :).

Seems like we're looking for a way to have different behaviour between a) first payment preprocessing and b) mandated payment preprocessing.

I'm not sure about using a flag (boolean) on the preprocessor. What if the preprocessor's default handle method has external effects other than modifying the OrderItemCollection (like with coupons)?

I feel there may be a need for a dedicated handleFirstPayment method on the preprocessor to cater for this.

But this would result in a BC.

How about adding a dedicated handleFirstPayment method to the preprocessors for handling firstPayments, which only gets called if the method_exists?

Thinking out loud here, not entirely convinced yet.

@sandervanhooft
Copy link
Collaborator

Seems like we're looking for a way to have different behaviour between a) first payment preprocessing and b) mandated payment preprocessing.

And c) optionally have the same behavior.

@SanderMuller
Copy link

The difficulty with this is finding a solution that keeps the package modular so it can suit as many use cases as possible.

In the case of offering addons through preprocessors, the first charge is basically the same as any recurring charge.

I think the most powerful option is when a preprocessor or coupon can decide if it should be applied on all charges, or only on the first charge, or only on recurring charges.

I have a very limited understanding of how the preprocessors and coupons work internally, but would it be an option to use Concerns for this? Let a Coupon(handler) or preprocessor determine if it should be added on the first payment by using a concern, like ShouldBeAppliedOnFirstPayment, similar to how Laravel Excel uses Concerns (https://docs.laravel-excel.com/3.1/architecture/concerns.html)

I don't have a good view on all the different use cases, and how a Concern structure effects all of them, though.

@sandervanhooft
Copy link
Collaborator

The charges may be the same on both the first payment Order and subsequent mandated Orders, but the business logic does not have to be. I.e. on a mandated payment you may want to reset a monthly bandwidth counter. Basically you don't want to persist anything on the first payment.

That's why I'm rooting for separating the handle() and the handleFirstPayment() methods.

Concerns/traits are a great way for adding modular behavior, but I'd go for capturing the logic first, then figure out if Concerns are a viable way to go here.

@sandervanhooft
Copy link
Collaborator

@RobertBoes what do you think?

@RobertBoes
Copy link
Contributor Author

I think more use-cases should be figured out, since I would assume the preprocessors would always be called, even on the first payment. But that's also where it gets complicated, since the first payment requires a different flow.

I don't have a complete overview how the first payment differs from a mandated payment, so it's quite difficult for me to know what exactly needs to be handled differently. For example the coupon and persist order, seems to me it should be handled the same on first and mandated payment. Storing the order on first payment makes sense, as you could list those orders in your application and show the user the order was cancelled. But there's probably a very good reason why the first payment does this differently and doesn't use the preprocessors right now.

@sandervanhooft
Copy link
Collaborator

There are essentially three scenario's in which the preprocessors should be used:

  1. Preparing the first payment. At this stage nothing is persisted yet, all is captured as actions and sent to Mollie in the payment metadata. The preprocessors are used to calculate the total payment amount. I advise to not trigger any persisted side-effects at this point.
  2. Processing (handling) the first payment once it's paid. The metadata is unwrapped into actions and these are executed. This is when the preprocessors should be executed and everything should be persisted.
  3. Creating a mandated payment. Everything should be firing and persisted.

@sandervanhooft
Copy link
Collaborator

I agree that in 99% (perhaps even 100%) of the cases the preprocessors should fire on both first payments and mandated payments, but there's an important distinction between scenario 1 and 2 in my previous message we should cater for.

@sandervanhooft
Copy link
Collaborator

sandervanhooft commented Oct 22, 2019

Perhaps simply having a prepareFirstPayment method pointing to the default handle method is enough. And override it where necessary.

@marnickmenting
Copy link

marnickmenting commented Mar 4, 2021

Any update on this, will this be attributed in v2? We have the same use case. Base subscription of x users per month, plus an add-on for using an integration for a fixed price per month. I would like to keep the same billing cycle for both, so using two separate subscriptions would break that.

Maybe the one-off payment function can be used for this too, to add the on-off payment every month before processing payment.

@lkmadushan
Copy link

We have the same issue here :(

@sandervanhooft
Copy link
Collaborator

We don't directly address this issue in v2.

But we do introduce the option of setting the payment mandate using a one-off charge. This way you can have a minimal first payment (add it to the customer's balance even) to obtain the mandate, and then start billing using mandated payments - and the preprocessors. I think that covers your use case.

I have also been seeing big companies like Stadia ask for a minimal payment amount for the mandate, start the free trial, and then refund that amount.

Note that there's a small fee for refunding transactions, so refunding may not be your cup of tea depending on your trial-to-paid conversion rate.

@sandervanhooft
Copy link
Collaborator

While I think that covers your use cases, I will keep this issue on the radar to see if we can further improve the customer and developer experiences later on (v3?).

@lkmadushan
Copy link

@sandervanhooft Thanks for your response and really appreciate your work. Is there a way we can get access to cashier-mollie v2? I saw that is a closed repo and not available the for public yet. We are implementing mollie integration for a Laravel application that we need to go live at end of this year. We can save time upgrading to v2 if you can give us access to the closed repo if possible.

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

No branches or pull requests

5 participants