What we need:
- The payment gateway plugin type
- The payment gateway config entity + UI
- The payment type YAML plugin
- The credit_card payment type
- The payment method content entity (for storing tokenized payment information), which uses the payment types as bundles
- The payment content entity, which uses payment types as bundles

The checkout flow plugin type + config entity combo introduced in #2689549: Implement the initial checkout structure is the model to copy.
We could also spin off the payment content entity to its own issue, focus only on the initial infrastructure.

Architectural notes

The main logic will go into commerce_payment_gateway plugins which will store their configuration in commerce_payment_gateway configuration entities.
The plugins will extend a base class (we can provide multiple) and implement a set of interfaces to indicate its own capabilities (supporting preauth, refunds, saving payment information). The plugin has complete freedom in how it’s structured internally, it can use an SDK, inject a Guzzle object, delegate to its own helpers, etc.
We should be flexible enough to support non-CC payments. Contrib should be able to implement Direct Debit (used for recurring in europe), or a two-step flow for checks (check received/deposited).

Terminology problem:
We need to name the entity that stores payment information for a user, as well, as its bundle.
This terminology is very uneven between payment gateways and other systems, mostly around how they define a payment method.
Here’s a definition given by Microsoft:

A payment method is the way in which customers pay for the items that they purchase on a Web site. Payment methods can include credit cards, gift certificates, purchase orders, cash cards, and custom methods.

But does that make a “credit card as a concept” a payment method, or is an actual credit card the payment method? Braintree thinks it’s an actual user’s card:
“A payment method represents transactable payment information such as credit card details or a customer's authorization to charge a PayPal or Venmo account. Payment methods belong to a customer”
Others use it to mostly to mean “the type of payment we support”, and name the saved payment information in some other way (Stripe alternates beteen calling it the payment source and the token, authorize.net calls it the payment profile, paymill calls it just “payment” which is weird).
Most major sites say “Payment method” to mean “your saved information”.

So, we have two options here:
1) “Payment method” is the saved payment information. The bundle comes from the “payment type” plugin (ala hooks in D7).
2) “Payment information” is again the saved payment information, the bundle comes from the “payment method” plugin.

Note: We decided to go with #1.

The second problem is whether to have a Payment entity or a Payment transaction entity.
Gateways tend to think in terms of transactions, users tend to think in terms of payments, I am unsure which version is easier for us developers.

Payment transactions are more low-level and look like this:
AUTHORIZE $100
CAPTURE $80
REFUND $40
REFUND $40

A transaction is immutable, performing an action on it (e.g. capturing an authorization) simply creates another one. It stores the remote transaction id, the remote status, and a message providing additional information (PayPal has 20 reasons why a payment might be pending, for example). Authorize, Capture, Refund are transaction types (bundles). They are grouped by payment type (credit card, direct debit). The status of a transaction is FAILURE, SUCCESS, PENDING.

This is exactly what Commerce 1.x did. Note that it is possible to capture less than what was authorized (the rest is returned), and it is possible to refund a single capture several times, until all of the money is gone. There’s no concept of “this transaction is all used up” (authorization captured, capture refunded), there might need to be.

With a payment entity you’d create a $100 payment and authorize it.
If you decided to capture $80, then that becomes the new $payment->amount.
If you refunded $30, the new $payment->amount is $50.
(We could also make refunds separate payments with a negative amount but I’m unsure I see a point).

A payment would have a workflow:
New -> Pending -> Complete -> Cancelled | (Partially) Refunded.
This workflow is generic enough for all payment types.
For credit cards, new means “I am about to redirect / authorize”. Pending means “we have an authorization that needs to be approved OR something is stuck on the gateway side”.

There would also be a boolean status to indicate whether the actual request failed, requiring a retry.
In this case the payment only has the amount, remote status and message for the last action, anything before that (authorization, a failed capture) is lost, which creates the need to have a log entity (e.g. the order level activity-log with a payment group that can be filtered on)

Note: We decided to go with a Payment entity.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

bojanz created an issue. See original summary.

Anonymous’s picture

Great goal

Anxious about ;)

Best regards
ArchGalileu

clemens.tolboom’s picture

bojanz’s picture

@clemens.tolboom
No, Payment module is its own thing.

andypost’s picture

There's great points about making offsite payments in payment module that already have 8.x version #2683795: [META] Add offsite payment methods support
Suppose commerce could collaborate with current implementation

niko-’s picture

Hi

Good points

I have started this issue for payment module with goals to simplify real payment methods implementations.

I want take your attantion on the following items.
1. I think we need add generic external result status plugin\method for offsite payment methods. Maybe it will be better make optional visitor visual page for each payment status. (I mean Complete/Cancelled/Failed/(Partially) Refunded)
2. Results urls on any real payment methods shoud not be dinamic like /payment/< hash >/return but /payment_method/< external status >.
3. I think we must use per payment methods result urls sets becouse in most cases we can't identify if data recived from GET\POST (ie from Request() ) is corresponding to certain payment method. I mean if Payment Id from POST is 12345 we need also validate if this payment was started with rigth payment methods.
4. Many offsite payments methods are not pinged site on any transaction statuses except success so we need "fallback" flag for payment status If this flag is TRUE for payment status it means that we need to emulate IPN like procecessing. Owervise payment transactions on site will stay on panding state even if payment real state must be fail.
5. We must handle multiple instances of the same payment methods with different configuration not in the way like that was in commerce 1.x (i.e. almost all contrib payment methods for commerce 1.x are not support this functionality because it not transparent for contrib developers)

More details you can find in this issue
Also can you describe me how I can help with propper payment methods implemetations in commerce 2.

clemens.tolboom’s picture

No, Payment module is its own thing.

A rephrase of my question: why not focus on integrate with payment module? And make that module rock too.

ricovandevin’s picture

I'd think that is a great idea too but for some reason the Drupal Commerce maintainers have decided against using Payment as the main payment platform for Commerce 2.x #1789160: Make Commerce 2.x depend on Payment.

Maybe this can be reconsidered, limiting the time that will pass before we can start processing payments for Drupal Commerce in Drupal 8. :-)

bojanz’s picture

@clemens.tolboom @ricovandevin
Using Payment would require me to rewrite it, to account for the additional use cases Commerce needs to satisfy. Neither Xano nor I need that at the moment.
Payment is a stable solution with its own user base, there's no need for me to introduce chaos.

clemens.tolboom’s picture

@ricovandevin thanks for the link.

Using Payment would require me to rewrite it

@bojanz I'm confused about the rewriting stuff after reading Xano reasoning in #1789160: Make Commerce 2.x depend on Payment and frankly I don't get it. It could be a great marriage inline with the DC effort to create reusable components like intl, pricing and tax. But I've no clues on impacts for DC using Payment. It would be great but we all need a working DC asap :-)

kurtfoster’s picture

@bojanz, any ideas on production ready release for Commerce, I'm trying to hold off some clients at the moment. I'd hate to start a D7 commerce site build now and see a D8 prod ready release half way through.

Sorry, wrong issue queue.

agoradesign’s picture

@kafmil: oh no, good issue queue - I wanted to ask @bojanz about the state of Payment API today anyway, just haven't seen him on IRC today at all :D

andypost’s picture

Last weekend I got hacked to cleanup/review api for off site payment modules in sendbox https://www.drupal.org/sandbox/niko/2685859
I think payment method configuration could be unified and compatible across both modules at least for ones that receives IPN requests
The general points are in issue summary #2683795: [META] Add offsite payment methods support
Any feedback welcome)

rszrama’s picture

For the last two weeks we've been working on IEF issues related to translations and revisions, so no major announcements here re: the payment API. As someone who had to manage a major project release before (Commerce 1.x : ), it can be somewhat discouraging to be asked for updates when none are given. Bojan will definitely update all issues as they're addressed, and while he'll be switching back to Payment work now that the more fundamental blockers have been dealt with, there's no guarantee that other fundamental (or core Drupal) issues won't come up that delay its finalization.

Right now I wouldn't expect a first pass at the payment API to merge in for at least a couple of weeks (basically, the time we were delayed).

(Also, thanks for sharing andypost, but we don't have any intention of depending on the Payment module.)

bojanz’s picture

Let's track the beta1 progress here: #2733869: Tag 2.0-beta1.
Product translations were a major blocker that took a week of full time work.
I'm making progress on a few more (attribute translations, revisions), then switching gears back to payments (which will take time, as Ryan says).

agoradesign’s picture

agoradesign’s picture

I have some architectural/conceptual questions. I've looked into the payment branch of the Github repo.

A payment gateway defines via annotation a single payment type that is supported/belongs to the gateway. As far as I can see, credit card will be a default payment type delivered with commerce. This is a trivial one. But when I think of third party payment gateways, that handle multiple different payment types (credit cards, direct banking, PayPal,...), how would I model this? Should I define an own gateway for every possible payment type, or do I rather define and reference one payment type, that unites all different real world types into one definition?

Sebastien M.’s picture

Hi all

I want to point a use case discovered recently. On Stripe payment method it is possible to use 3D Secure to limit any fraud.
cf : https://www.drupal.org/node/2717383

This is a In-Site payment method, but to type the secure code we need to be redirected to a page provided by a bank using form posted using an additional like checkout step as for Off-Site payment method.
If secure code is refused we need to go back to the checkout step which display payment methods to try again, eventually with a different credit card or payment method.
If the code is validated, the initialized payment transaction is validated.

Currently in Drupal 7 it's possible but really hard to implement such behavior.
It should be a very good thing to keep it in mind while creating the architecture of this module.

My thanks

agoradesign’s picture

That's a good point. The 3D Secure thing causes headache all the time. We have a customer running a Magento store. The payment extension opens the 3D secure form after sending the order :DD

bradjones1’s picture

Just an FYI, looks like Bojanz has been busy: https://github.com/drupalcommerce/commerce/commit/dece89ae59c075095654c5...

Thanks for the hard work and things are looking good! Excited to read any notes about the status of this branch vis-a-vis getting others in there to test and improve.

agoradesign’s picture

bojanz updated the payment branch on Github today, but still unfinished. For everyone, who wants to start working based on that anyway, a patch might be easier to integrate into deployment than code hosted on a specific branch -> here's a patch covering the current state of work, working against the current dev branch

agoradesign’s picture

@bojanz: as you're not on IRC at the moment, here's a very quick feedback of the recent changes:

  • there's still one bug in the workflow definition: the "to" state of "refund" must be named "capture_refunded"
  • commerce_order and commerce_payment now have a circular dependency on each other in their info files
  • something I often struggle myself with designing more complex system: I dislike the new dependency of commerce_order on commerce_payment. It's manageable though, but not pretty - think of very simple (b2b) order systems without any payment, or even projects, where Commerce woudl be a good base, if you need just products and the cart feature, but no real checkout and payment
  • although it interestingly worked to put an item in my cart - why payment_gateway and payment_method fields on order entity marked as required? That's impossible to fulfill, as long the order is a cart and not yet transformed into a placed order
bradjones1’s picture

First, big thanks to bojanz and the rest of the Commerce Guys and maintainers for their hard work. I've been working on some e-commerce-y projects at the moment and am trying to help where I can in the issue queues. You guys are all over entity, address, wow!

Anyway, to this point:

something I often struggle myself with designing more complex system: I dislike the new dependency of commerce_order on commerce_payment. It's manageable though, but not pretty - think of very simple (b2b) order systems without any payment, or even projects, where Commerce woudl be a good base, if you need just products and the cart feature, but no real checkout and payment

The bolded piece above is an interesting concept for me. I am working on integrating paid memberships to a central "membership hub" that manages access to affiliated sites. I have created a dependency on commerce_order so I can take advantage of the content type and also the workflow model. However, all the transactions and recurring payment magic happens at our vendors - both of whom have very different (read: archaic) data models. I don't need to reflect the payment attempts or data in Drupal, just the status of the current "order" in the foreign system.

Having the other modules enabled and unused isn't a huge deal, though not ideal. Just a vote for a more decoupled use case.

agoradesign’s picture

rebased the patch

agoradesign’s picture

rebased the patch

agoradesign’s picture

added the missing "payment_gateway" field to payment entity

bradjones1’s picture

@agoradesign - You can get a current patch for a branch by just appending .patch to the comparison; e.g.:

https://github.com/drupalcommerce/commerce/compare/payment.patch

agoradesign’s picture

Thanks Brad. I know. But that's too volatile. Any rebase etc changes the patch. So your builds may break.

  • bojanz committed 88b10f2 on 8.x-2.x
    Issue #2711013: Implement the initial payment functionality
    
bojanz’s picture

Status: Active » Fixed

Also merged #2785523: Add a UI for payment methods separately.
And committed the rest!

Checkout is still not fully functional, so I'm continuing to push followups.
The offsite API is still missing and will be added in the upcoming days.
Tracked in: #2785591: Add an API for offsite payments.

The braintree module is currently at https://github.com/bojanz/commerce_braintree/
It can be used as an example for the stripe and authorize.net integrations.

Status: Fixed » Closed (fixed)

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