Skip to content

Instantly share code, notes, and snippets.

@Verurteilt
Created March 15, 2016 19:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Verurteilt/253662b56df9f1e9b635 to your computer and use it in GitHub Desktop.
Save Verurteilt/253662b56df9f1e9b635 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
### INTEGRACION Django
try:
import imp
imp.find_module('settings') # Assumed to be in the same directory.
except ImportError:
sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n" % __file__)
sys.exit(1)
from settings import setup_environ
import settings
parser, options, args = setup_environ(settings, usage=u'%prog [options] start|stop|restart')
###
import logging
import logging.handlers
import os
import time
from django import db
from django.conf import settings
from django.core.management.color import color_style
from django.db import transaction
from General.utils.daemon import Daemon
# =-=-=-=-=-=-=-=-=-=-=-=-=-=- COLORES
os.environ['DJANGO_COLORS'] = settings.DJANGO_COLORS
style = color_style()
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-
ARCHIVO_LOG = 'ipn.log'
# =-=-=-=-=-=-=-=-=-=-=-=-=-=- MENSAJES
MSJ_INICIANDO_SERV = style.HTTP_INFO('Iniciando servicio de validacion IPN.')
MSJ_TERMINANDO_SERV = style.HTTP_INFO('Terminando servicio de validacion IPN.')
MSJ_ERROR_SERV = style.ERROR('Ocurrio un error en el servicio: %s.')
MSJ_INICIANDO_VERIFICACIONPP = 'Iniciando verificacion con PayPal...'
MSJ_RESPUESTA_VERIFICACIONPP = 'Respuesta de PayPal: %s'
MSJ_CONEXION_MONGODB = 'Realizando conexion con MongoDB...'
MSJ_IPN_SIN_VERIFICAR = 'IPN %s se encuentra sin verificar (%s)'
MSJ_TRANSACCION_YA_PROCESADA = 'Transaccion %s ya se encuentra registrada y procesada en transacciones'
MSJ_IPN_A_VERIFICAR = 'Verificando IPN %s'
MSJ_IDENTIFICANDO_PAGO = 'Identificando pago %s...'
MSJ_IPN_VERIFICADAS = 'Se han terminado de verificar IPNs'
MSJ_VERIFICANDO_INFO_IPN = 'Verificando informacion IPN...'
MSJ_PROCESANDO_TRANSACCIONPP = 'Procesando transaccion PayPal...'
MSJ_TRANSACCION_EXITOSA = 'Se proceso la transaccion %s exitosamente'
MSJ_NO_CUSTOM = style.NOTICE('\'custom\'') + ' no encontrado, intentando identificar el pago mediante \'item_number\' (%s) e \'item_name\' (%s)...'
MSJ_OBTENIENDO_CUENTA_GRAL = 'No se tiene la informacion necesaria para identificar el pago, obteniendo una cuenta general para asignarlo...'
MSJ_IDENTIFICANDO_FORMA_PAGO = 'Identificando forma de pago PayPal...'
MSJ_TRANSACCION_NO_VALIDA = 'La transaccion no cumple con los requerimientos minimos para ser procesada, '#payment_status %s y mc_currency %s'
MSJ_TRANSACCION_NO_VALIDA_PAYMENT = MSJ_TRANSACCION_NO_VALIDA + 'payment_status %s y mc_currency %s'
#MSJ_TRANSACCION_NO_VALIDA_SUBSCR = MSJ_TRANSACCION_NO_VALIDA + 'referencia %s y operador %s'
MSJ_TRANSACCION_NO_PROCESADA = 'No se pudo procesar la transaccion %s exitosamente'
MSJ_ERROR_OBTENCION_CUENTAPP = 'No se pudo determinar la cuenta PayPal para verificar el pago'
MSJ_ERROR_OBTENCION_CARGO = 'El cargo %s no existe en MiUTEL'
MSJ_ERROR_PARAMETROS_INSUFICIENTES = 'No se tiene la informacion necesaria para verificar, identificar y procesar el pago: %s'
MSJ_ERROR_OBTENCION_CUENTA_GRAL = 'No se puede determinar una cuenta/usuario en donde aplicar el pago'
MSJ_ERROR_GRAL = 'Ocurrio un error al procesar IPN: %s'
MSJ_SUSCRIPCION_YA_PROCESADA = 'Suscripcion %s ya se encuentra registrada y procesada en suscripciones'
MSJ_ERROR_OBTENCION_OPERADOR = 'El operador que invito a domiciliar, no existe en MiUTEL, se registrara operacion sin operador: %s'
MSJ_ERROR_OBTENCION_OPERADOR2 = 'No se tiene informacion del operador que invito a domiciliar, se registrara operacion sin operador'
MSJ_SUBSCR_PAYMENT_NO_PROCESADO = 'Pago por suscripcion no se procesa hasta tener informacion de la suscripcion %s'
# =-=-=-=-=-=-=-=-=-=-=-=-=-=- LOGGER
logger = logging.getLogger('%s.%s' % ('validaripn', __name__))
handler = logging.handlers.RotatingFileHandler(ARCHIVO_LOG, maxBytes=10000000, backupCount=20)
formatter = logging.Formatter('%(asctime)s:%(name)s:%(levelname)s - %(message)s')
logger.setLevel(logging.INFO)
handler.setFormatter(formatter)
logger.addHandler(handler)
class IPN(Daemon):
def __init__(self, pidfile, tiempo_ejecucion=60):
self.tiempo_ejecucion = tiempo_ejecucion
super(IPN, self).__init__(pidfile)
def verificar_ipn(self):
import re
from bson import ObjectId
#from django.contrib.auth.models import User
from General.mongodb import cliente_mongodb
from pagos.interpretes.base import buscar_concepto_banco
from Simoba.paypal import fechahorapp_a_fechahoralocal
from Simoba.paypal.transaccion import Transaccion
from Simoba.paypal.models import TransaccionPayPal, CuentaPaypal, SubscriptionPayPal, CatTipoSuscripcionPayPal
#from registro.models import Cargo, Moneda
from General.utils import desencripta_rsa
logger.info(MSJ_CONEXION_MONGODB)
#cliente_mongodb = MongoDB(host='mongodb://%s:%s@%s/%s' % (settings.MONGO_USER, urlquote(settings.MONGO_PWD), settings.MONGO_HOST, settings.MONGO_DB))
#cliente_mongodb.collection = cliente_mongodb[settings.MONGO_DB].paypal_ipn
#cliente_mongodb = MongoDB(settings.MONGO_USER, settings.MONGO_PWD, host=settings.MONGO_HOST, source=settings.MONGO_DB)
#cliente_mongodb.collection = cliente_mongodb.db.paypal_ipn
ipn_recibido_q = {'estado': TransaccionPayPal.TRANS_IPN_RECIBIDO}
ipn_procesado_q = dict(payment_status='Completed', **ipn_recibido_q)
ipn_verificado_q = {'$set': {'estado': TransaccionPayPal.TRANS_IPN_VERIFICADA}}
#ipn_recibidos = cliente_mongodb.collection.find({
ipn_recibidos = cliente_mongodb.PaypalIpn.find({
'$or': [
{'$and': [ipn_recibido_q, {'payment_status': 'Completed'}]},
{'$and': [ipn_recibido_q, {'txn_type': 'subscr_signup'}]}
]
})
_es_subscription = lambda txn_type: txn_type.lower() == TransaccionPayPal.TRANS_SUBSCR_SIGNUP.lower()
_es_subscr_payment = lambda txn_type: txn_type.lower() == TransaccionPayPal.TRANS_SUBSCR_PAYMENT.lower()
for ipn in ipn_recibidos:
try:
transaccion_pp = None
subscription_pp = None
es_subscription = _es_subscription(ipn['txn_type'])
es_subscr_payment = _es_subscr_payment(ipn['txn_type'])
logger.info(MSJ_IPN_A_VERIFICAR % style.HTTP_INFO(
es_subscription and ipn['subscr_id'] or ipn['txn_id']))
# hasta el momento solo se manejan suscripciones y pagos
if es_subscription or es_subscr_payment:
try:
subscription_pp = SubscriptionPayPal.objects.select_related('cuentapaypal').get(subscriptionid=ipn['subscr_id'])
# continuar con el siguiente IPN solo si se
# trata de una suscripcion (subscr_signup)
if es_subscription:
cliente_mongodb.PaypalIpn.update(dict(_id=ObjectId(ipn['_id']), subscr_id=ipn['subscr_id'], **ipn_recibido_q), ipn_verificado_q)
logger.info(MSJ_SUSCRIPCION_YA_PROCESADA % ipn['subscr_id'])
# no debe procesarse IPN
continue
except SubscriptionPayPal.DoesNotExist:
subscription_pp = SubscriptionPayPal(subscriptionid=ipn['subscr_id'])
if es_subscr_payment:
logger.warning(MSJ_SUBSCR_PAYMENT_NO_PROCESADO % style.NOTICE(ipn['subscr_id']))
continue
if not es_subscription:
try:
transaccion_pp = TransaccionPayPal.objects.select_related('cuentapaypal').get(transactionid=ipn['txn_id'])
if transaccion_pp.cat_estado_id == TransaccionPayPal.TRANS_PROCESADA:
cliente_mongodb.PaypalIpn.update(dict(_id=ObjectId(ipn['_id']), txn_id=ipn['txn_id'], **ipn_procesado_q), ipn_verificado_q)
logger.info(MSJ_TRANSACCION_YA_PROCESADA % ipn['txn_id'])
# no debe procesarse IPN
continue
except TransaccionPayPal.DoesNotExist:
pass
logger.info(MSJ_IPN_SIN_VERIFICAR % (
style.HTTP_INFO(es_subscription and ipn['subscr_id'] or ipn['txn_id']),
style.HTTP_INFO(ipn['txn_type'])
))
# los siguientes valores deben existir para
# verificar, identificar y registrar el pago
# o la suscripcion
url = ipn['urlencode']
mc_currency = ipn['mc_currency']
mc_gross = not es_subscription and ipn['mc_gross'] or '?'
payment_status = not es_subscription and ipn['payment_status'] or '?'
payment_date = not es_subscription and ipn['payment_date'] or '?'
subscr_date = es_subscription and ipn['subscr_date'] or '?'
receiver_email = ipn['receiver_email']
# los siguientes valores pueden o no estar disponibles
custom = ipn.get('custom')
item_number = ipn.get('item_number')
item_name = ipn.get('item_name')
# concepto_banco = None
# referencia = None
# operador = None
# subscr_licenciatura_id = None
cuenta_paypal = getattr(transaccion_pp, 'cuentapaypal', None) or CuentaPaypal.objects.obtener_cuenta_paypal(receiver_email=receiver_email)
#if cargo_id is None:
# si el tipo de transaccion es pago por/o suscripcion PayPal, intentar
# buscar la referencia del usuario en 'item_number' o 'item_name'
if es_subscr_payment or es_subscription:
logger.warning(MSJ_NO_CUSTOM % (style.NOTICE(item_number or '-'), style.NOTICE(item_name or '-')))
# # obtener la licenciatura en caso de que
# # haya sido especificada en la suscripcion:
# # esto nos permitira realizar una consulta
# # mas especifica en la obtencion de una referencia
# if ipn.get('option_selection1'):
# match_licenciatura = re.match(r'^.+ ([0-9]+)$', ipn['option_selection1'])
# subscr_licenciatura_id = match_licenciatura and match_licenciatura.group(1)
# # primero intentar con 'item_number'
# if item_number is not None:
# referencia = self.obtener_referencia(item_number.strip(), cuenta_paypal.cuenta_banco_id, licenciatura_id=subscr_licenciatura_id)
# match = None
# # a traves de 'item_name' puede obtenerse la referencia, en
# # pagos por suscripcion (subscr_payment), o el operador que
# # realizo una invitacion para suscripcion, en caso de
# # suscripcion (subscr_signup)
# if item_name is not None:
# match = re.match(r'^[a-zA-Z ]+ ?(\d+)?$', item_name)
# # si no se encontro referencia con 'item_number',
# # intentar con 'item_name'
# if referencia is None and match and match.group(1) is not None:
# referencia = self.obtener_referencia(match.group(1).strip(), cuenta_paypal.cuenta_banco_id, licenciatura_id=subscr_licenciatura_id)
# # el id del operador que envio la invitacion
# # puede estar incluido en 'item_name'
# if es_subscription:
# if match and match.group(1):
# try:
# operador = User.objects.get(pk=match.group(1))
# except User.DoesNotExist, e:
# logger.error(MSJ_ERROR_OBTENCION_OPERADOR % ' | '.join((unicode(e), 'pk=%s' % match.group(1))))
# else:
# logger.warning(MSJ_ERROR_OBTENCION_OPERADOR2)
# # solo se muestran estos mensajes y se
# # realizan busquedas adicionales en caso de que
# # la transaccion sea un pago (subscr_payment)
# else:
# if referencia is None:
# logger.warning(style.NOTICE(MSJ_OBTENIENDO_CUENTA_GRAL))
# else:
# logger.info(MSJ_IDENTIFICANDO_FORMA_PAGO)
# try:
# concepto_banco = buscar_concepto_banco(cuenta_paypal.cuenta_banco_id, ipn)
# except IndexError:
# raise Exception(MSJ_ERROR_OBTENCION_CUENTA_GRAL)
logger.info(MSJ_INICIANDO_VERIFICACIONPP)
transaccion_paypal = Transaccion(
cuenta_paypal.apiusername,
desencripta_rsa(cuenta_paypal.apipassword),
desencripta_rsa(cuenta_paypal.apisignature),
cuenta_paypal.cat_apipaypal.url_paypal + '/cgi-bin/webscr',
cuenta_paypal.cat_apipaypal.version
)
respuestapp = transaccion_paypal.validate_ipn(url).read()
transaccion_paypal.close() # cerrar transaccion PayPal
logger.info(MSJ_RESPUESTA_VERIFICACIONPP % style.HTTP_INFO(respuestapp))
if respuestapp == 'VERIFIED':
# iniciar verificacion, identificacion y registro de pago
logger.info(MSJ_VERIFICANDO_INFO_IPN)
# parametros para actualizacion en caso de error
params_update = {'_id': ObjectId(ipn['_id'])}
if not es_subscription:
params_update.update(dict(txn_id=ipn['txn_id'], **ipn_procesado_q))
if payment_status == 'Completed' #and mc_currency in Moneda.habilitados.values_list('clave', flat=True):
logger.info(MSJ_IDENTIFICANDO_PAGO % style.HTTP_INFO(str(custom)))
#cargo = getattr(transaccion_pp, 'cargo', None) or (cargo_id is not None and Cargo.objects.get(pk=cargo_id) or None)
logger.info(MSJ_PROCESANDO_TRANSACCIONPP)
respuesta_paypal = {
'paymentinfo_0_transactionid': ipn['txn_id'],
'paymentinfo_0_amt': mc_gross,
'paymentinfo_0_paymentstatus': payment_status,
}
respuesta_paypal.update(ipn)
transaccion_paypal = self.procesar_transaccion(cuenta_paypal, concepto_banco, respuesta_paypal, cargo,
referencia=referencia, es_subscr_payment=es_subscr_payment, subscription_pp=subscription_pp)
if transaccion_paypal.es_procesada():
cliente_mongodb.PaypalIpn.update(params_update, ipn_verificado_q)
logger.info(MSJ_TRANSACCION_EXITOSA % style.HTTP_SUCCESS(transaccion_paypal.transactionid))
continue # procesar otra transaccion
logger.error(MSJ_TRANSACCION_NO_PROCESADA % style.ERROR(ipn['txn_id']))
else:
logger.error(MSJ_TRANSACCION_NO_VALIDA_PAYMENT % (style.ERROR(payment_status), style.ERROR(mc_currency)))
else:
params_update.update(dict(subscr_id=ipn['subscr_id'], **ipn_recibido_q))
#if referencia is not None:
logger.info(MSJ_PROCESANDO_TRANSACCIONPP)
subscription_pp.usuario_id = referencia and referencia.usuario_id
subscription_pp.operador = operador
subscription_pp.fecha_suscripcion = fechahorapp_a_fechahoralocal(subscr_date)
subscription_pp.cuentapaypal = cuenta_paypal
subscription_pp.licenciatura_id = subscr_licenciatura_id
subscription_pp.tipo_suscripcion = CatTipoSuscripcionPayPal.habilitados.get(clave=ipn['txn_type'])
subscription_pp.num_pagos = ipn.get('recur_times', 0)
subscription_pp.monto = ipn['mc_amount3']
subscription_pp.save()
cliente_mongodb.PaypalIpn.update(params_update, ipn_verificado_q)
logger.info(MSJ_TRANSACCION_EXITOSA % style.HTTP_SUCCESS(subscription_pp.subscriptionid))
continue # procesar otra transaccion
#else:
#logger.error(MSJ_TRANSACCION_NO_VALIDA_SUBSCR % (style.ERROR(unicode(referencia)), style.ERROR(unicode(operador))))
# si se llega a este punto, la transaccion
# no pudo procesarse exitosamente
if params_update:
cliente_mongodb.PaypalIpn.update(params_update, {'$set': {'estado': TransaccionPayPal.TRANS_IPN_INVALIDO}})
except CuentaPaypal.DoesNotExist:
logger.error(MSJ_ERROR_OBTENCION_CUENTAPP)
except Cargo.DoesNotExist:
logger.error(MSJ_ERROR_OBTENCION_CARGO % style.ERROR(str(cargo_id)))
except KeyError, e:
logger.error(MSJ_ERROR_PARAMETROS_INSUFICIENTES % style.NOTICE(', '.join(e.args)))
except Exception, e:
logger.error(MSJ_ERROR_GRAL % style.ERROR(unicode(e)))
#cliente_mongodb.desconectar()
logger.info(MSJ_IPN_VERIFICADAS)
@transaction.commit_on_success
def procesar_transaccion(self, cuenta_paypal, concepto_banco, rpp, cargo, referencia=None, **kwargs):
from decimal import Decimal
from conciliacion import registrar_deposito_operacion
from paypal import fechahorapp_a_fechahoralocal
from paypal.helper import procesar_transaccion_paypal
from paypal.models import SubscriptionTransaccionPayPal
fechapago_deposito = fechahorapp_a_fechahoralocal(rpp['payment_date'])
params_f = dict(params_deposito=dict(fecha_pago=fechapago_deposito, cuenta_id=cuenta_paypal.cuenta_banco_id))
es_subscr_payment = kwargs.pop('es_subscr_payment', False)
subscription_pp = kwargs.pop('subscription_pp', None)
#if cargo is None:
if es_subscr_payment:
referencia = referencia or concepto_banco.referencia
usuario = referencia.usuario
deposito = registrar_deposito_operacion(
usuario,
#usuario.obtener_cuenta(referencia.licenciatura_id, cuenta_paypal.cuenta_banco_id),
usuario.obtener_cuenta(cuenta_banco=cuenta_paypal.cuenta_banco_id),
Decimal(rpp['mc_gross']),
dict(
cat_forma_pago_id=concepto_banco.forma_pago_id,
folio_pago=rpp['txn_id'],
conciliador_id=2294, # por el momento admin.admisiones@utel.edu.mx es el conciliador
referencia_unica=referencia.referencia,
**params_f['params_deposito']
)
)
params_f.update(dict(usuario=usuario, deposito=deposito, licenciatura_id=referencia.licenciatura_id))
else:
params_f.update(dict(cargo=cargo))
transaccion_paypal = procesar_transaccion_paypal(rpp, cuenta_paypal.pk, **params_f)
# en caso de ser un pago por suscripcion,
# relacionarlo con la suscripcion
if es_subscr_payment:
SubscriptionTransaccionPayPal.objects.create(subscriptionid=subscription_pp, transactionid=transaccion_paypal)
return transaccion_paypal
def obtener_referencia(self, referencia, cuenta_banco_id, licenciatura_id=None):
from pagos.interpretes.base import obtener_referencia_con_cargos_mas_antiguos
from registro.models import Referencia
try:
referencias = Referencia.habilitados.filter(referencia__exact=referencia, cuenta_banco=cuenta_banco_id)
if licenciatura_id is not None:
referencias = referencias.filter(licenciatura=licenciatura_id)
return obtener_referencia_con_cargos_mas_antiguos(referencias)
except Referencia.DoesNotExist:
pass
return None
def run(self):
while True:
try:
self.verificar_ipn()
except Exception, e:
logger.error(style.ERROR(unicode(e)))
db.reset_queries() # limpia querysets
db.close_connection() # cierra conexiones a BD
time.sleep(self.tiempo_ejecucion)
if __name__=='__main__':
try:
demonio = IPN('/tmp/ipn.pid')
#demonio = IPN('/var/run/ipn.pid', tiempo_ejecucion=1800)
if len(args) == 1:
if args[0] == 'start':
logger.info(MSJ_INICIANDO_SERV)
demonio.start()
elif args[0] == 'stop':
logger.info(MSJ_TERMINANDO_SERV)
demonio.stop()
elif args[0] == 'restart':
demonio.restart()
else:
print style.ERROR('Opción no válida')
sys.exit(2)
sys.exit(0)
else:
print style.HTTP_SUCCESS(parser.get_usage())
sys.exit(2)
except Exception, e:
logger.error(MSJ_ERROR_SERV % unicode(e))
sys.exit(1)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment