Уперся в тупик с проектированием приема платежей

Обсуждаем, как правильно строить приложения
Ответить
SindBad
Сообщения: 81
Зарегистрирован: 2015.06.18, 10:53

Уперся в тупик с проектированием приема платежей

Сообщение SindBad »

Всем здравия!
И так, имеется в голове примерно такое:

1) interface PaymentApiInterface(array credentials) - от него наследуются адаптеры к различным внешним платежным шлюзам.

2) Действующий (пока будет активным только один) адаптер прописываются в контейнер со своими секретными доступами и т. п.

3) class Payment extends \yii\db\ActiveRecord - AR-модель платежа: покупатель, сумма, состояние и пр.

4) abstract class PaymentService - создает Payment, расширяется для создания платежей разных субъектов оплаты разными типами пользователей (пополнение баланса, покупка чего-то, оплата услуг одного типа, другого типа - для всего этого создается свой сервис).

5) abstract class PaymentApiService(PaymentApiInterface, Payment) - умеет выполнять необходимые задачи через PaymentApiInterface, например fetchPaymentUrl() - получать от банка URL для редиректа на оплату. Так же может расширяться для оплат разных субъектов (потому что будут отличаться способы получения данных платежа и плательщика, способы генерации failURL, successURL, backendURL, описания платежа).

6) И вот так это планировалось использовать:

Код: Выделить всё

<?php
class PaymentAction extends \yii\base\Action
{
    //пример упрощен
    public function run()
    {
        //сначала отрабатывает PaymentService, создает модель, заполняет, сохраняет
        $serviceInstance = new PaymentService(Payment::class);
        $serviceInstance->createModel(Yii::$app->getRequest()->getBodyParams());
        if ($serviceInstance->save()) {
            //на сцену выходит PaymentApiService, он, используя данные модели Payment и текущий PaymentAPI,
            //генерирует запрос к банку и приносит нам paymentURL для редиректа
            $paymentApi = Yii::$app->get('PaymentAPI');
            $payment    = $serviceInstance->getModel();
            $merchant   = new PaymentApiService($paymentApi, $payment);
            $paymentUrl = $merchant->fetchPaymentUrl();
            $response   = Yii::$app->getResponse();
            
            $response->setStatusCode(201);
            return ['paymentURL' => $paymentUrl];
        } elseif ($serviceInstance->modelIsValid()) {
            throw new ServerErrorHttpException(
                'Непредвиденная ошибка в ходе сохранения'
            );
        }
        //это REST API, сериалайзер сам обработает ошибки валидации
        return $serviceInstance->getModel();
    }
}
Мне, в общем, всё нравилось до момента, когда я понял, что плагины PaymentAPI будут по-разному формировать запросы из разного набора данных модели Payment. Которые, зараза, тоже разные.
Например, $payment->user->phone может быть, а может и не быть. А может, PaymentAPI он вообще будет не нужен. Может, будет нужен $payment->user->surname, который вообще есть не у всех типов user. А вдруг $payment->user->homeAddress понадобится, а его вообще нет и не будет?
Короче, полиморфизм ломается на взаимодействии плагина PaymentAPI и модели Payment.

Как можно выйти из такой ситуации (ну, кроме жесткой стандартизации user'ов)?
BalykhinAS
Сообщения: 179
Зарегистрирован: 2018.02.05, 13:41
Контактная информация:

Re: Уперся в тупик с проектированием приема платежей

Сообщение BalykhinAS »

Добавьте поля платильщика для каждого метода оплаты. Payment должен знать как извлечь часть полей с модели User а какие поля дополнительно заполняются в момент оформления заказа. И откровенно я пока полиморфизм в вашей схеме не увидел.
SindBad
Сообщения: 81
Зарегистрирован: 2015.06.18, 10:53

Re: Уперся в тупик с проектированием приема платежей

Сообщение SindBad »

BalykhinAS писал(а): 2021.05.31, 09:15 И откровенно я пока полиморфизм в вашей схеме не увидел.
В этом и состоит проблема, он не выстраивается. Не получается, чтобы производные PaymentApiService могли использовать производные PaymentApiInterface, не вдаваясь в детали и условия. Ну, либо это я уже зашился в детали и условия и не вижу каких-то очевидных простых вещей.
SindBad
Сообщения: 81
Зарегистрирован: 2015.06.18, 10:53

Re: Уперся в тупик с проектированием приема платежей

Сообщение SindBad »

BalykhinAS писал(а): 2021.05.31, 09:15 Добавьте поля платильщика для каждого метода оплаты. Payment должен знать как извлечь часть полей с модели User а какие поля дополнительно заполняются в момент оформления заказа.
Ок, как выяснилось, поля плательщика - это даже не самое страшное. Самое неприятное то, что у меня производный сервис от PaymentApiService будет зависеть от шлюза PaymentApiInterface.
И если я в контейнере поменяю шлюз TinkoffPaymentApi на SberPaymentApi, я буду в сервисах ProductPaymentApiService, AdvertPaymentApiService, AccountPaymentApiService переписывать fetchPaymentUrl(), потому что SberPaymentApi получает ссылку по-другому. А YandexPaymentApi вообще генерирует ее сам, без обращений к шлюзу.
Это всё теоретические выкладки, но вот так, например, получает данные для платежа TinkoffPaymentApi на практике:

Код: Выделить всё

$this->init([
            'Amount',
            'OrderId',
            'IP',
            'Description',
            'CustomerKey',
            'Recurrent',
            'SuccessURL',
            'FailURL',
            'NotificationURL',
            'PayType',
            'Receipt' => [
                'Phone',
                'Email',
                'Taxation',
                'Items' => [
                    [
                        'Quantity',
                        'Price',
                        'Amount',
                        'Name',
                        'PaymentMethod',
                        'PaymentObject',
                        'Tax',
                    ],
                ],
            ],
        ]);
У другого шлюза будет такая же масса других параметров, среди них - неизвестно еще какие и как вычисляемые налету.
Не укладывается пока в голове, как отвязать PaymentApiService и при этом не передавать в аргументах fetchPaymentUrl все на свете возможные параметры для всех на свете платежных шлюзов)

А если весь fetchPaymentUrl перенести в шлюз, то опять же - эти аргументы, эти связи с побочными моделями, этого всего не должно там быть.
Ответить