суббота, 9 июля 2011 г.

django-qiwi. Платежная система на django - киви. SOAP-сервер

Итак, как же установить и запустить платежную систему qiwi на django. Делаем все по шагам и у вас получится.
Здесь не рассматриваются шаги по регистрации в системе qiwi. Здесь рассматривается техническая часть (Хотелось бы только сказать, что в кабинете qiwi нужно включить soap)

Скачиваем и устанавливаем приложение

  1. Скачиваем отсюда https://github.com/satels/django-qiwi/zipball/master
  2. Распаковываем
  3. Устанавливаем
    python setup.py install
Или же все одним шагом
pip install -e git://github.com/satels/django-qiwi.git#egg=django-qiwi

Делаем установки django-qiwi в ваш проект.

В settings.py django-проекта
INSTALLED_APPS = [
    ...
    'django_qiwi',
    ...
]

#qiwi soap
QIWI_APP = 'qiwi'
QIWI_LOGIN = '12345'
QIWI_PASSWORD = 'SecretPAroli'
QIWI_SOAP_SERVER = ('0.0.0.0', 17555)

Здесь вы указываете QIWI_APP backend-приложение (app), который будет обрабатывать ваши объекты счетов и платежей (про это ниже)
Про логин и пароль все ясно.
Далее, QIWI_SOAP_SERVER - здесь указывается хост и порт, на которых будет запускаться qiwi soap сервер для обработки оповещений со стороны QIWI.

Интегрируем с вашей системой платежей (счета, платежи)

  1. Прикручиваем форму для создания платежа (Мы сообщаем QIWI о создании счета)
  2. Делаем Backend-приложение (QIWI сообщает нам - удачно или нет прошел платеж)

Форма создания платежа

Вот как обрабатывается у меня создания платежа
from django_qiwi import get_status_text
from django_qiwi.soap.client import Client as QiwiClient
...
                client = QiwiClient()
                code = client.createBill(
                    phone=client_phone,
                    amount=sum,
                    comment=comment,
                    txn=invoice.id,
                    lifetime=datetime.now() + timedelta(1),
                )
                if code in [0, 50]:
                    return redirect(invoice.get_qiwi_url())
                else:
                    messages.error(request,
                       MESSAGE_QIWI_ERROR % get_status_text(code)
                    )
                    buy_denial_by_invoice(invoice)
...

Видно, что создаем платеж и оповещаем через soap - оповещаем друзей роботов из QIWI,
при удачном исходе - код 0 или 50 - переадресуем клиента на страницу с инструкцией, как оплатить, или же сообщаем о ошибке и отменяем платеж.

Что же такое указано в QIWI_APP?

Теперь, как же будет нас оповещать qiwi о удачном платеже. Будет через backend, который мы создали и указали в переменной QIWI_APP
Указан пакет в вашем проекте django. Структура такая:
qiwi
    __init__.py
или такая
qiwi.py
Содержание qiwi примерно такое будет у вас (Я взял из своего проекта)
from discounts.utils import buy_denial_by_invoice, buy_paided_by_invoice
from django.conf import settings
from django_qiwi.soap.client import Client as QiwiClient
from invoices.models import Invoice
import datetime
import logging


def update_bill(txn, status):
    client = QiwiClient()
    response = client.checkBill(txn)
    code = response['status']
    amount = response['amount']
    amount = float(amount)
    invoices = Invoice.objects.filter(
        id=txn, status=Invoice.STATUS.nopaid, type='qiwi',
    )
    if invoices.exists():
        invoice = invoices[0]
        if code == 60:
            buy_paided_by_invoice(invoice, amount)
            ret = 0
        elif code in [150, 160, 161]:
            buy_denial_by_invoice(invoice)
            ret = 0
        else:
            ret = -1
    else:
        ret = 210
    logging.basicConfig(filename=settings.QIWI_LOG_FILE, level=logging.DEBUG)
    dt = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    message = 'date: %s, txn: %s, code: %s, amount: %s, ret:%s' % (
        dt, txn, code, amount, ret
    )
    logging.debug(message)
    return ret

Т.е. вот здесь проверяется при оповещении о платеже - есть ли неоплаченный платеж, то если есть, то оплачиваем его, если пришло оповещение о оплаченном платеже или же не оплачиваем.
Такж же можем сообщить, что платежа не существует

Запускаем тесты

При каждом последующем изменении кода - запускаем тесты, чтобы точно ничего не отвалилось при оплате у клиента
Создаем подмену для QIWI - мы будем отсылать оповещения ему о создании платежа
#coding:utf-8
from django_qiwi.soap import server
from settings import QIWI_PASSWORD, QIWI_LOGIN
from threading import Thread
import SOAPpy
import qiwi

class QiwiProviderThread(Thread):

    def __init__(self):
        Thread.__init__(self)
        self.setDaemon(True)
        self.server = self._createSOAPServer()
        self.data = {}
        self.paid = False

    def _createSOAPServer(self):
        server = SOAPpy.SOAPServer(('localhost', 77877))

        def createBill(login, password, user, amount, comment, txn, lifetime, alarm, create):
            if not all([login==QIWI_LOGIN, password==QIWI_PASSWORD]):
                return 150
            if len(user) != 10:
                return 150
            try:
                amount = float(amount)
            except:
                return 150
            if amount > 15000:
                return 242
            if alarm not in [0, 1, 2]:
                return 150
            if create not in [0, 1]:
                return 150
            self.data = {
                'user': user,
                'amount': amount,
                'comment': comment,
                'txn': txn,
                'lifetime': lifetime,
                'alarm': alarm,
                'create':create,
                'date': datetime.now().strftime("%d.%m.%Y %H:%M:%S")
            }
            return 50

        def checkBill(login, password, txn):
            if txn != self.data['txn']:
                return 210
            res = dict(self.data, **{
                'status': self.paid and 60 or 52
            })
            return res

        server.registerFunction(createBill)
        server.registerFunction(checkBill)
        return server

    def run(self):
        self.server.serve_forever()

    def stop(self):
        self.server.shutdown()
        del self.server
        self.join()

    def bayWithQiwi(self, txn, amount):

        lifetime = self.data['lifetime']

        if datetime.strptime(lifetime, '%d.%m.%Y %H:%M:%S') < datetime.now():
            raise ValueError(u'Нельзя оплатить. Срок оплаты прошел')

        self.paid = True
        self.data['amount'] = str(amount)

        qiwi.update_bill(txn=txn, status=60)

- запускается поток, который запускает подмену soap серверу системы. Даже есть подмена клиенту, который пошел оплачивать через терминал QIWI! (bayWithQiwi). Сам тест
class QiwiBayTest(test.TestCase, _InvoiceTest, _BayTest, _QiwiTest):

    def setUp(self):
        super(QiwiBayTest, self)._setUp()
        self.qiwi_provider = QiwiProviderThread()
        self.qiwi_provider.start()
        initial = self.default_initial
        self.default_initial = dict(initial, **{'payment_method': 'qiwi'})

    def tearDown(self):
        super(QiwiBayTest, self)._tearDown()
        self.qiwi_provider.stop()

    def testBayWithQiwiAmountLessThenPrice(self):
        """Оплачиваем скидку через QIWI, но кладем меньшую сумму, чем надо"""
        response = self._postData(self.default_initial)
        invoice = self._assertExistQiwiInvoice()
        self._assertFollowToQiwiInvoicePage(response, invoice)
        amount = self.discount.price_paid*0.8
        self.qiwi_provider.bayWithQiwi(invoice.id, amount)
        self._refreshUser()
        invoice = self._getRefreshedInvoice(invoice)
        self._assertParentInvoiceNoPaid(invoice)
        self._assertUserBalance(amount + self.initial_sum)
        invoice.delete()

    def testBayWithQiwiAmountGreateThenPrice(self):
        """Оплачиваем скидку через QIWI, но кладем большую сумму, чем надо"""
        data = self.default_initial
        response = self._postData(data)
        invoice = self._assertExistQiwiInvoice()
        self._assertFollowToQiwiInvoicePage(response, invoice)
        amount = self.discount.price_paid*data['number'] - self.initial_sum
        self.qiwi_provider.bayWithQiwi(invoice.id, amount)
        self._refreshUser()
        invoice = self._getRefreshedInvoice(invoice)
        self._assertParentInvoicePaid(invoice)
        self._assertUserBalance(0.0)
        invoice.delete()

    def testBayWithQiwiAndBalanceGreateThenPrice(self):
        """Оплачиваем скидку через QIWI, при этом баланс на счете большой"""
        data = self.default_initial
        sum = self.discount.price_paid*data['number']
        update_balance(self.user, sum)
        balance_before = self.user.get_profile().get_balance()
        response = self._postData(data)
        invoice = self._assertExistQiwiInvoice()
        self._assertFollowToQiwiInvoicePage(response, invoice)
        amount = self.initial_sum
        self.qiwi_provider.bayWithQiwi(invoice.id, amount)
        self._refreshUser()
        invoice = self._getRefreshedInvoice(invoice)
        self._assertParentInvoicePaid(invoice)
        self._assertUserBalance(balance_before + amount - sum)
        invoice.delete()

    def testSomeErrorOnQiwiProvider(self):
        """Ошибка на сервере QIWI, например, превышена сумма платежа(> 15000)"""
        data = dict(self.default_initial, **{
            'number': int(16000.0/self.discount.price_paid)
        })
        response = self._postData(data)
        self._assertFollowToBayPage(response)
        self._assertCountMessages(response, 1)
        invoice = self._assertExistQiwiInvoice()
        self._assertParentInvoiceDenial(invoice)

    def _assertExistQiwiInvoice(self):
        return self._assertExistInvoiceByType('qiwi')
Не забудьте в файле test_settings.py (настройках settings.py для тестов) указать примерно такое from settings import * QIWI_SOAP_ISHOP_URL = 'http://localhost:77877' QIWI_SOAP_SERVER = ('localhost', 17777) А потом запускать так ./manage.py test --settings="test_settings.py"

Запускаем сервер soap для взаимодействия с qiwi

Чтобы просто попробовать, как работает - запускаем так: python manage.py run_qiwi_soap В продакшн версии нужно запускать подобное через sv (что такое sv): Используем файл из приложения django_qiwi: https://github.com/satels/django-qiwi/blob/master/qiwi_soap_server.sh.example Далее, создаем /etc/service/qiwi_soap_server/run и делаем его исполняемым, копируем содержание файла qiwi_soap_server.sh.example в него

Запускаем
sv u qiwi_soap_server

Все должно работать. Пишите комментарии

13 комментариев:

  1. Хотелось бы увидеть пример реализации invoice.get_qiwi_url()

    ОтветитьУдалить
  2. class Invoice(models.Model):
    def get_qiwi_url(self):
    return reverse('invoices_invoice_type', kwargs={
    'type': "qiwi", 'invoice_id': self.id
    })

    ОтветитьУдалить
  3. Это страница с инструкцией об оплате

    ОтветитьУдалить
  4. Спасибо. Я думал что это ссылка на сайт киви, где сразу же можно оплатить одним из предложенных способов. На сайте киви это называется гибридный способ. К примеру form.target как это сделанно в django-robokassa.

    ОтветитьУдалить
  5. Долбаёб, научись писать! Учи русский язык, нихуя не понтно, уёбок? тебе прогать нельзя - ты опасен и туп? дибил сраный.!!!!!

    ОтветитьУдалить
  6. Долбоёб* понятно* ты опасен и туп!* дебил* (с горяча ошибок понаделал ^^)

    ОтветитьУдалить
  7. Undefined Coder = Непонятливый кодер. Если тебе не понятно, то это твое проблемы.

    ОтветитьУдалить
  8. Ну ты дибил, undefined - значит не определён, и если рассматривать имя и фамилию как переменные, то считай что определил var name = "Coder", surname = undefined;) вот ссыль http://translate.google.ru/#ru|en|%D0%BD%D0%B5%D0%BF%D0%BE%D0%BD%D1%8F%D1%82%D0%BD%D1%8B%D0%B9%0A%D0%BD%D0%B5%D0%BF%D0%BE%D0%BD%D1%8F%D1%82%D0%BB%D0%B8%D0%B2%D1%8B%D0%B9 (или юзай словарь)

    ОтветитьУдалить
    Ответы
    1. ну ты и мразь. пиздец только у нас есть ёбнутое мудачье которое считает что опен-сорс ему еще че-то должен!! просто феерический мудак

      Удалить
  9. Но независимо от перевода, ты остаёшься непонятливым

    ОтветитьУдалить
  10. Не надо обвинять людей в чём-либо, если просто не можешь донести свою мысль понятно каждому! И насчёт непонятливости: я программирование понимаю абстрактно и конкретно, а вот твоё пояснение и не абстрактно и не конкретно. В добавок.... мне уже пох, я прочёл нормальные исходники php работы с qiwi через soap (где были комментарии к коду), и вот что скажу, коротких комментариев было достаточно, чтобы хоть что-то понять, и ушло на это буквально минута! так что суди сам, о том, как ты составляешь пост, если исходники на другом языке (php) проще понять, чем твой пост с примерами кода на нужном мне языке (python)

    ОтветитьУдалить
    Ответы
    1. Ты чего вообще несешь, если тебе не хватает мозгов понять что тут написано, то не надо тебе это читать. Программируй на своем php и не надо оскорблять автора, мне его код очень помог

      Удалить