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 payments support. #1203

Merged
merged 19 commits into from
May 15, 2014
Merged

Conversation

kayue
Copy link
Contributor

@kayue kayue commented Mar 14, 2014

@QuingKhaos
Copy link
Contributor

👍

@stloyd stloyd added this to the 1.0.0-BETA1 milestone Mar 14, 2014
@stloyd
Copy link
Contributor

stloyd commented Mar 14, 2014

  • Do we really need bidirectional relation here? IMO not (and with this change we don't need additional interface),
  • we should have additional method that checks the amount in payment(s) is same as total "price" of order,
  • IMO hasPayments() can be removed, it's just syntetic sugar we don't use anywhere,
  • where you would call the hasPayment()? TBH. I don't see any use case right now,

@pjedrzejewski
Copy link
Member

This is how I always imagined multiple payment support.

  • Order can have many Payments. (as opposed to current Order.payment)
  • In the checkout, just like for shipments, we render a method selection form for every payment.
    So, if someone customizes the payment factory (or we implement something in core in future) the user has to select a payment method for every payment. (try adding more than 1 shipment to the order right now, it will render more forms)
  • Of course, only "new" state payments are taken into account in the checkout payment step.
  • In purchase step, we loop through payments and every single payment is handled (just like we do now, just multiple times) until all payments are not "new".
  • If payment is failed for some reason, the customer can retry paying through external controller. (outside the checkout) and NEW payment is created for this payment, we keep the other "failed" payment for history purposes.
  • To check if order is paid, we simply check if successful payments total is equal to order total. (basically, if balance is zero)
  • I think we could use Payment with negative amount as refund. (with state refund)
  • I'm on the edge about bidirectional relation... For sure the Payment.order should be nullable, I'd love to have an ability to just create Payment in the backend (without order) to charge customer. (give him a link to pay etc.)

Some random thoughts about recurring payments and remembering payment methods.

  • I already have introduced CreditCard model. Basically, payment gateways which are capable of token billing, should save the CreditCard (PaymentSourceInterface) and attach it to the user with a token. Payment has both "method" and "source" properties, so in checkout, we'll have additional form with list of user payment sources. If he selects source instead of a method, he'll be billed automatically via token.

I can assure you that all this is possible, because I implemented it in Sylius app already. (just not the way I'd like it to be in core - quality, coupling)

Thoughts?

@kayue
Copy link
Contributor Author

kayue commented Mar 14, 2014

@stloyd

Do we really need bidirectional relation here?

Why not bidirectional? It is very useful to be able to access order payments without injecting a repository.

... with this change we don't need additional interface

Did you mean PaymentInterface? We need to add this interface because PaymentsBundle is not depending on OrderBundle.

We should have additional method that checks the amount in payment(s) is same as total "price" of order,

What do you think about this PR? #1131

IMO hasPayments() can be removed, it's just syntetic sugar we don't use anywhere,
where you would call the hasPayment()? TBH. I don't see any use case right now.

I agree it is redundant, I was just copying shipments' implementation here.
FYI We call hasPayment in addPayment, making sure it won't duplicate.

Will make the changes @pjedrzejewski suggested..

@pjedrzejewski
Copy link
Member

Thanks for starting on this @kayue, you're awesome!

@kayue
Copy link
Contributor Author

kayue commented Mar 14, 2014

In purchase step, we loop through payments and every single payment is handled (just like we do now, just multiple times) until all payments are not "new".

@pjedrzejewski I am not sure is this can work. At the moment, a new payment is created every time customer visit Payment Step. This means there are multiple payment associated with order already (just missing the linkage). All of these payment state are new or pending, except the successful one.

Are we going to change this behaviour?

@kayue
Copy link
Contributor Author

kayue commented Mar 14, 2014

@pjedrzejewski Just a reminder, we cannot rely on payment state failed, because this state is not supported by all payment gateway, and even the gateway supports it, only customer who cancel payment properly will trigger the failed state. Otherwise the payment will remain as new.

@winzou
Copy link
Contributor

winzou commented Mar 14, 2014

failed != cancelled

@pjedrzejewski
Copy link
Member

@kayue Is #823 the change we're talking about? I think payment step should create new payment only if there is no other payment with state "new". (assuming we have 1:n relation)

@kayue
Copy link
Contributor Author

kayue commented Mar 14, 2014

@pjedrzejewski Yes.

Can I do front-end in another PR because it is a lot more complex, and it is not a must have.

Multi-payment can still be useful in backend when it comes to order refund: #1202

What do you think?

if (!$this->hasPayment($payment)) {
$payment->setOrder($this);
$this->payments->add($payment);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you make this fluid please? Same with the next method.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Richtermeister Fluid? what does it means?

@winzou
Copy link
Contributor

winzou commented Apr 14, 2014

@kayue what is the status on this? :)

@kayue
Copy link
Contributor Author

kayue commented Apr 14, 2014

@winzou Not much, there was too many changes happening in Sylius in the past few weeks, and I wasn't able to adopt them to my project yet.

Feel free to work on this if you wish.

@winzou
Copy link
Contributor

winzou commented Apr 14, 2014

I'm currently upgrading my sylius, and it's actually quite smooth!
Will try to have a look, but this idea of 24 hours in one day is definitely
not enough!

Alexandre Bacco

+65 8671 7859
http://alex.bacco.fr

On Mon, Apr 14, 2014 at 4:20 PM, Ka Yue Yeung notifications@github.comwrote:

@winzou https://github.com/winzou Not much, there was too many changes
happening in Sylius in the past few weeks, and I wasn't able to adopt them
to my project yet.

Feel free to work on this if you wish.

Reply to this email directly or view it on GitHubhttps://github.com//pull/1203#issuecomment-40338972
.

@kayue
Copy link
Contributor Author

kayue commented Apr 17, 2014

Let me give this a 2nd try this weekend

@kayue
Copy link
Contributor Author

kayue commented Apr 17, 2014

@pjedrzejewski Will you accept if I only deprecated the getPayment method instead of removing it?

https://github.com/kayue/Sylius/blob/c47ab403236e5b8344d276ce6d644949e9aba4a2/src/Sylius/Component/Core/Model/Order.php#L319-L333

public function getPayment()
{
    return $this->getPayments()->last();
}

Removing getPayment() method require massive amount of work and tests, we will need to revamp the entire checkout process... It is more than what I am capable of.

Can I only focus on creating 1:n order payment relationship here?

@kayue
Copy link
Contributor Author

kayue commented Apr 20, 2014

@pjedrzejewski @winzou What do you guys think?

In order to support real multiple payment like you said ("loop through payments until all payments are not "new"), we will need to tweak both PurchaseStep and PayumBundle. I think it deserve another pull request.

@kayue kayue changed the title [WIP] Multiple payments support. Multiple payments support. Apr 20, 2014
@@ -108,7 +109,9 @@ public function voidOrderPayment(GenericEvent $event)
);
}

$order->getPayment()->setState(PaymentInterface::STATE_VOID);
if (false === $order->getPayments()->isEmpty()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just !$order->getPayments()->isEmpty()

@winzou
Copy link
Contributor

winzou commented Apr 20, 2014

This is a bit weird, as the aim of this PR is just to add the ability to use multiple payments, but it does not add multiple payments. To be honest this leads to some wtf in the code. I think it's a bit risky to have this merged like this, as we won't be able to identify what is proper code and what is temporary workaround.

@kayue is it possible for you to adapt all the logic of the different actors (paymentProcessor, listeners, etc. no need to go up to the purchase step) to be able to really handle multiple payments?

@winzou
Copy link
Contributor

winzou commented Apr 20, 2014

To be clear: what is done is good, a nice step towards multiple payments support. Thanks for that! But there are some temporary workarounds in listeners and processors that deserve to be dealt with before merging I think :)

@kayue
Copy link
Contributor Author

kayue commented Apr 20, 2014

@winzou Sounds fair, I agree we should remove all the temporary workaround. I believe this list has everything we want to fix already: https://scrutinizer-ci.com/g/Sylius/Sylius/inspections/023d68fa-c0e7-4d63-958d-79f2d98dea26/issues/

They can all be fixed by changing Payum's SecurityToken model from Order to Payment. After that we can completely remove the getPayment() and setPayment() method in Order.

Did I missed anything here?

@winzou
Copy link
Contributor

winzou commented Apr 20, 2014

I was thinking of giving the payment in addition to just the order on all the listeners and processor modified in your PR. The payments->last() shouldn't be used. We should always know which payment is to be changed.

@kayue
Copy link
Contributor Author

kayue commented May 8, 2014

@pjedrzejewski I agree what you says, but at the current stage it is safer to just get the last "new" payment. I think there is a "bug" in Sylius where an order could have multiple "new payment" when user trying to pay with different payment method. (maybe I am using an older version).

As discussed earlier we will not implement multiple payments UI logic in this PR. We can do it in another PR.

@pjedrzejewski
Copy link
Member

@kayue Deal, let's go with the getLastPayment() method for now. :)

@kayue
Copy link
Contributor Author

kayue commented May 9, 2014

@pjedrzejewski This PR is ready!

@pjedrzejewski
Copy link
Member

@kayue Awesome work, looks good to me, but I'd love to get some feedback from @makasim. And from @winzou, because not sure which PR should go first - which will be easier to update afterwards. (state machine or this one)

@winzou
Copy link
Contributor

winzou commented May 10, 2014

Good to me as well! You can go with this one first, mine is blocked by
Finite.
Le 10 mai 2014 16:34, "Paweł Jędrzejewski" notifications@github.com a
écrit :

@kayue https://github.com/kayue Awesome work, looks good to me, but I'd
love to get some feedback from @maksim https://github.com/maksim. And
from @winzou https://github.com/winzou, because not sure which PR
should go first - which will be easier to update afterwards. (state machine
or this one)


Reply to this email directly or view it on GitHubhttps://github.com//pull/1203#issuecomment-42736219
.

*
* @return PaymentInterface
*/
public function getLastNewPayment();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about: getLastPayment(PaymentInterface::STATE_NEW)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stloyd I don't think it will works because it is not supported by PropertyAccess in form.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why? If you call without value, it will use default one, so it will be PaymentInterface::STATE_NEW.

@makasim
Copy link
Contributor

makasim commented May 10, 2014

Awesome work, looks good to me, but I'd love to get some feedback from @makasim.

I'd be able to review it on Monday. Dont hesitate to ping me.

* Check if the payment subject has certain payment.
*
* @param PaymentInterface $payment
* @return Boolean
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add new line to stop CS fixed complain about this =)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also IIRC we use @return bool same as Symfony.

@kayue
Copy link
Contributor Author

kayue commented May 12, 2014

@stloyd Fixed, thanks for reviewing.
@makasim Please have a look if you have time, I don't want to break anything here. Thank you very much! :)

*
* @author Ka Yue Yeung <kayuey@gmail.com>
*/
class Payment extends BasePayment implements PaymentInterface
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to mention. This could be named OrderAwarePayment and moved to order or payment component. I dont a big fun of dependencies on core component.

See for the referance #964

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, but let's do this in a separate PR! This one is big enough. :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pjedrzejewski sure, just mentioned it

@makasim
Copy link
Contributor

makasim commented May 13, 2014

Great job @kayue.

@kayue
Copy link
Contributor Author

kayue commented May 14, 2014

Thanks for review @makasim. I have fixed the comments.

@pjedrzejewski Good to merge?

pjedrzejewski pushed a commit that referenced this pull request May 15, 2014
@pjedrzejewski pjedrzejewski merged commit bb64dac into Sylius:master May 15, 2014
@kayue kayue deleted the feature/payment-order branch May 15, 2014 08:23
@pjedrzejewski
Copy link
Member

Awesome awesome awesome... thank you Ka Yue! 👍

@winzou
Copy link
Contributor

winzou commented May 15, 2014

Great!
Let's rebase state machine PR now.

@pliashkou
Copy link

Good work!!

pamil pushed a commit to pamil/Sylius that referenced this pull request May 7, 2019
pamil pushed a commit to pamil/Sylius that referenced this pull request May 7, 2019
pamil pushed a commit to pamil/Sylius that referenced this pull request May 7, 2019
pamil pushed a commit to pamil/Sylius that referenced this pull request May 7, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Enhancement Minor issues and PRs improving the current solutions (optimizations, typo fixes, etc.).
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

9 participants