Problem/Motivation

Few days ago I try to implement robokassa payment integration https://www.drupal.org/project/2681671/git-instructions (not finished yet)
I found that payment method plugins of payment 8.x are not supported the following:
1. Offsite redirect payment form (POST/GET).
2. IPN/interuction/rusult callback do not exist
3. IPN data validation method/callback do not exist
4. Static success/fail/panging routes do not exist (Not all payment systems support dynamic back urls and I think that this is bad behaviour to send back urls in payment form in most cases).
5. Payment->execute() method always set "pending" status before payment processing. That's OK for onsite payment methods but that's KO for offsite payment methods.

You must know that ~99% real online payment systems are required offsite redirect to process the payment because to get payment card info (card number, CVV, card owner) without redirect site must have PCI DSS cert.

I have searched the most generic workflow for offsite payment methods (check attached screanshort)
This workflow is used by all payment systems that I know. (Paypal, Paybox, PayU, Interkassa, Robokassa .... )
i.e. generic workflow the following:
1. Visitor submit payment on site.
2. Site create payment form according to order info and show "payment form" with sign and payment info according to payment system requirements.
3. User submit this form by himself or form will be autosubmited by js so payment process will start on payment system side.
4. Visitor fill info required by payment system and start the real payment process (auth+capture or auth with delayed capture)
5. When payment will be processed payment system will send IPN/interaction/result request to static site url configured in metchant account and we need to check if sign and payment data OK and change payment status. Only on IPN request we must change payment status. Many country specific payment systems ping IPN only on success payment result.
6. Anyway visitor will redirect to success/fail/panding (if status supported) page. In some cases we need fallback call IPN callback for correct process fail status here.

So now payment module not support most usable payment process in good order.

My investigation shows that now for cover this workflow in each payment method module we need to add
1. Payment redirect form controller
2. Controllers for static success / fail / panding pages. i.e. http://site.com/payment/complete (now http://site.com/payment/{payment_number}/complete)
3. Controller for static IPN url
4. IPN validator method
5. Redirect to site payment form must be implemented in getPaymentExecutionResult() method becouse $payment->execute() add redirect for executed payments only.
6. PaymentMethodBase->executePayment() always set pendding status before real execution by payment method class so IPN request can't call payment execute and must implement own logic in controller
7. IPN validator should be implemented as public although mutch better use protected for it
8. We can't call payment execute() twice becouse it always start process from the same status.

In general similar situation with payments availible in :
1. commerce 1.x (you can check my fixed implemlementations in https://www.drupal.org/project/commerce_robokassa and https://www.drupal.org/project/commerce_interkassa versions 2.x)
2. commerce 2.x seems payments not implemented yet
3. payment 7.x-1.x
3. payment 8.x-2.x

As result of this setuation on site payments created only on success processing or leave in pending state. So manager can't differentiate panding and fail payments so can't fully investigate payment issues with payment system support.

In all known for me payment method modules except commerce_robokassa 2.x and commerce_interkassa 2.x similar problems availible becouse error in example module.

Proposed resolution

We need to cover most usable offsite payment method workflow in 8.x.
1. Extend PaymentMethodBase with PaymentMethodBaseOffsite class for offsite payment methods and add Interface for it
2. in PaymentMethodBaseOffsite add following methods

  • IpnValidator()
  • IpnProcess()
  • redirectForm()

3. Add routes for result pages like payment/{external_status} - (they may reditect to payment/{payment}/complete with additional logic there.
4. add External Status plugins and seperate execute methods for each status
5. Add seperate static route like /payment/ipn for IPN/interaction/result process.
6. Refactor payment->execute() method to react to current payment status. I think this method should call different payment method methods according to new payment status. (something like commerce next/prev in checkout)

Remaining tasks

  • Discussing
  • Patching

User interface changes

  • none

API changes

  • see Proposed resolution section

Data model changes

  • none

Comments

niko- created an issue. See original summary.

niko-’s picture

Title: Add » [META] Add offsite payment methods support
xano’s picture

One of the primary goals of Payment is reject functionality that is specific to certain payment methods. Such functionality should be part of those payment methods or helper libraries/modules.

With regards to off-site payments, \Drupal\payment\Plugin\Payment\Method\PaymentMethodInterface::executePayment() returns \Drupal\payment\OperationResultInterface, which can be used to perform redirects if those are necessary for the completion of payment execution. The same pattern is also used for captures and refunds. It is the calling code's responsibility to handle this redirect, for instance by passing the Response object back to the controller, or send the URL to the client.
POST redirects can be achieved by either making this Response object include a form that submits itself off-site. When fed back to the controller, it will render this form in the client, which can then be submitted.

With regards to IPNs: this is an example of functionality that is too specific to include in Payment, so it should live in a separate module, or just become part of your payment method's module if it cannot be reused by any other payment methods.

niko-’s picture

> One of the primary goals of Payment is reject functionality that is specific to certain payment methods.

I undestood this but if you check code of payment module and my code in sendbox you can find following:
1. I implement executePaymentResult method in my class with redirect to payment form but payment will set current maybent entity as completed to proceed redirect. So payment will be executed when IPN request come from payment system. and if I start the execution again pending status will duplicate.

2. about IPN and rest:
> With regards to IPNs: this is an example of functionality that is too specific to include in Payment,
Please give me any example offsite payment system which do not require interaction callback on site. Sure, logic that must be implemented on IPN call is different for each payment systems. But we need universal callback that will be call payment method plugin method to process IPN request
3. Please give me example offsite payment system which do not require payment form.
4. Please give me example offsite payment system in integration example of which dynamic return urls will used.

Are you agree that for off site payment systems common things the following:
1. IPN or similar callback to process payment
2. In payment system tech documentation we can find something like "Please fill success/fail/interaction urls with http://yoursite.com/success http://yoursite.com/fail"
3. On site payment redirect form required for start payment process.

Are you agree that all points above is responsability of payment method plugin rather then controller in this module?
Are you agree that all points above must be implemented in payment method plugin not in controller in this module?
Are you agree that if 99% offsite payment method plugins must implement points above it will be better to implement universal way of integration and add methods into payment method plugins to simplify future usage.

niko-’s picture

> The same pattern is also used for captures and refunds.
Both this operations do not need to comunicate with site visitor so generic behaviour the following:
1. admin submit form in admin area
2. payment method call doCapture or doRefund method
3. wait for response.
4. redirect to any url you need, even dynamic becouse we know payment entity

Problem that I describe in this issue is more complex and sounds like "any payment methods in Drupal not ready to use offsite payment systems" So authors of condrib payment method integration need to implement the "magic thing". For better undestanding you can compare code of commerce_robokassa 2.x and 1.x. Similar situation in payment module.
The goal of this issue is implement payment integration way that will not break brain for developers

niko-’s picture

I have started implemeting this fuctionality in this senbox https://www.drupal.org/sandbox/niko/2685859

niko-’s picture

xano’s picture

Status: Active » Fixed

Thanks for the update! I'll list that module and the payment methods on Payment's project page :)

Status: Fixed » Closed (fixed)

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