Прикручиваем PayPal к магазину. Python

Сегодняшний вечер можно с гордостью назвать "вечером ебли с палкой", потому что он полностью был проведен в пейпеловской песочнице. Как бы итогом всего будет этот пост для гиков.

Имеем: Pylons (да не важно, любой python-фреймворк), PayPal Business Account (вот такой дают только по кредиткам США), кофе. Два. Ну и еще сервер, торчащий наружу, потому что нужно будет обрабатывать ответы от PayPal.

Хотим: прикрутить кнопочку "платить через PayPal" на сайт, чтобы пользователи несли нам денюжки со своих кредиток не задумываясь. Кстати, платить, можно с любой карточки, в том числе и российской. Лишь бы правильного типа (не Electron, как всем дают). Classic вполне подойдет. А еще можно оформить за 500 рублей MasterCard Classic в Связном. 500 рублей сразу на счет идут. Привязывается и к WM и к PayPal. Поцаны пробовали, вон как прутся.

Для начала как это работает, если кто как и я, работал с системами оплаты первый раз: мы формируем post-запрос к скрипту на сайте PayPal (легче всего это сделать через форму с кучей hidden-полей), обратно на URL уже нашего скрипта (переданный в форме, либо настроенный на сайте) поступает ответ. Так как и на первом и на втором шаге злоумышленники могут нам дать пизды, мы вручную шлем на PayPal еще один post-запрос с полученными данными. Типа спросили "это твое говно?" Если получаем в ответ VERIFIED, значит его, если INVALID, то не его, либо уже старо, оплачено, да в любом остальном случае. Это называется IPN (Instant Payment Notification). Затем проверяем полученные данные (вдруг заплатили меньше, чем мы хотели) и записываем в базу флажок "оплачено" и всю информацию. Ну вот и все. Так работают, кстати, почти все. WebMoney, с некоторыми отступлениями, тоже.

Для начала форма. Как-то так:

<form action="https://www.sandbox.paypal.com/cgi-bin/webscr" method="post">

<input type="hidden" name="cmd" value="_xclick">

<input type="hidden" name="business" value="mail@gmail.com">

<input type="hidden" name="lc" value="US">

<input type="hidden" name="item_name" value="My Cool Shop. Order #12345">

<input type="hidden" name="item_number" value="12345">

<input type="hidden" name="amount" value="200">

<input type="hidden" name="no_note" value="1">

<input type="hidden" name="no_shipping" value="1">

<input type="hidden" name="rm" value="1">

<input type="hidden" name="return" value="http://site.ru/order/12345">

<input type="hidden" name="cancel_return" value="http://site.ru/order/12345">

<input type="hidden" name="currency_code" value="USD">

<input type="hidden" name="notify_url" value="http://site.ru/paypal_listener">

<input type="submit" value="Платить через PayPal">

</form>

Здесь:
action = sandbox. Не забудьте сменить на просто paypal.com, когда будете выкладывать в продакшен.
cmd - чтобы PayPal понял, что это за запрос. Просто клик.
bussiness - email, он же ваш логин на PayPal. Не забудьте про бизнесс-аккаунт.
lc - страна, которая будет стоять по-умолчанию при оплате
item_name - название покупаемого продукта. Я просто хранил № заказа и название магазина.
item_number - ID заказа. Либо артикул товара. Любые символы, но я предпочел номером.
amount - любимое поле. Цена!
no_note, no_shipping - всякие плюшки пейпала типа рассчета доставки. Нам не нужны.
return, cancel_return - куда переадресовать в случае успешной или отмененной операции.
currency_code - что за валюта у нас в поле anount
notify_url - URL того самого IPN-листенера, код которого описан далее.

Теперь IPN-листенер. Тот самый, что по http://site.ru/paypal_listener. Простой пайлонсовский контроллер:

def paypal_confirm(self):

""" PayPal IPN listener """

import urllib2, urllib

# Тот самый POST-запрос к PayPal'у, чтобы подтвердить правильность

params = urllib.urlencode(request.POST)

url = "https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_notify-validate&" + params

response = urllib2.urlopen(url).read()

if response == "INVALID":

# И значит кто-то все сделал не правильно

return "FFFFFUUUUU"

# А если мы здесь, значит запрос был правильный.

if request.params.get("payment_status", "") == "Completed":

# Помните мы передали его в форме? Вот он и вернулся. ID заказа.

id = int(request.params.get("item_number", 0))

# Вдруг такого заказа вообще не существует в нашей базе

order = self.connection.shop.orders.find_one({ "id": id })

if not order:

return "FFFFUUUU"

# Но злые хакеры могли подменить цену в нашей форме.

# Обязательно проверьте, последствия сами понимаете

if float(request.params.get("mc_gross", -1)) >= float(order.get("price", 0) + order.get("tax", 0)):

# Ну а если мы добрались до сюда, значит все круто!

# Делаем что хотим с заказом

self.connection.shop.orders.update({ "id": id }, ... })

return "OK"

По комментариям должно быть понятно назначение каждого блока. PayPal'у откровенно похуй, что мы ответим (лишь бы хедер 200 OK), поэтому дальше дело наше - заносить в базу, проверять, что угодно. Хотя я бы для пущей секкурности потребовал бы еще какой-нибудь ответ для PayPal. Типа "данные успешно занесены в базу, списывай бабло". Хотя это их не интересует, так что тут особо аккуратно, чтобы не потерять заказы и не восстанавливать по письмам клиентов и логам PayPal'а.

И я бы рекомендовал класть в базу все полученные данные. Чтобы если вдруг что, оправдаться перед покупателями куда делись деньги.

Кстати, есть в песочнице сервис для теста IPN-запросов. Вы указываете что слать и куда, PayPal шлет, а вы смотрите что у вас не работает (для этого ведите логи). Вот тут: https://developer.paypal.com/devscr?cmd=_ipn-link-session

И это только со стороны кажется так просто. А у PayPal'а гигабайты документации по этому поводу, и отыскать то, что нужно нам - довольно сложно. Только чтобы освоить этот самый IPN-интерфейс мне пришлось прочитать 67-страничный pdf.

А вообще, главная прелесть, что вам даже не нужно спрашивать у заказчиков Business аккаунт, все можно сделать из песочницы. Хотя, честно сказать, она сделана очень "в духе PayPal". То есть нихрена не понятно, куча кнопок-ссылок, менюшки с 20 уровнями вложенности, и тормозит.

ReDetection — 04.08.2010 - 01:14 [80.64.175.96] Linux
этот пост - это ко всему прочему задел для прикручивания этой кнопочки себе в блог или ты пока не планировал?
V@s3K — 04.08.2010 - 01:15 [178.49.15.6] Linux
ReDetection, не планирую :)
Hast — 04.08.2010 - 04:34 [194.187.149.145] Windows
А я знал что Вася рано или поздно введёт платную подписку на свой бложек :'D
adVISeR — 17.08.2010 - 00:30 [90.151.66.137] Windows
С Visa Electron кстати всё отлично оплачивается, надо просто нормальный банк и дополнительную бумажку, в которой ты пишешь, что "согласен на всё, осознаю, что могу быть идиотом и оплатить что-нибудь мошенникам". И всё.
Антон — 25.05.2011 - 00:29 [78.108.79.76] Windows
Прикручу paypal к вашему сайту! icq 3463386
ReDetection — 25.05.2011 - 07:27 [89.189.191.13] Linux
среди школьников в маленьких городах типа кургана пэйпэл моднее, чем похапе?
refresh

(не заполняйте это поле)

i