from datetime import datetime, timedelta from optparse import make_option from django.core.management.base import BaseCommand, CommandError import braintree import requests from braintree.util.crypto import Crypto from lib.brains.management.commands.samples import webhooks from lib.brains.models import BraintreeSubscription from lib.transactions.models import Transaction from payments_config import products from solitude.logger import getLogger log = getLogger('s.brains.management') valid_kinds = [ 'subscription_charged_successfully', 'subscription_charged_unsuccessfully', 'subscription_canceled', ] class Command(BaseCommand): """ This is a crude way to generate and test webhook notifications. It does this by grabbing a sample request from Braintree and then reformatting it with some local data. There are some inherent problems with this:
from django.shortcuts import get_object_or_404 from rest_framework.decorators import api_view from rest_framework.response import Response from lib.buyers.forms import PinForm from lib.buyers.models import Buyer from lib.buyers.serializers import (BuyerSerializer, ConfirmedSerializer, VerifiedSerializer) from solitude.base import log_cef, NonDeleteModelViewSet from solitude.errors import FormError from solitude.logger import getLogger log = getLogger('s.buyer') class BuyerViewSet(NonDeleteModelViewSet): queryset = Buyer.objects.all() serializer_class = BuyerSerializer filter_fields = ('uuid', 'active') @api_view(['POST']) def confirm_pin(request): form = PinForm(data=request.DATA) if form.is_valid(): buyer = form.cleaned_data['buyer'] confirmed = False if buyer.pin == form.cleaned_data['pin']:
import os import sys from optparse import make_option from django.conf import settings from django.core.management.base import BaseCommand import boto from boto.s3.key import Key from solitude.logger import getLogger log = getLogger('s.s3') def push(source): if not all(settings.S3_AUTH.values() + [ settings.S3_BUCKET, ]): print 'Settings incomplete, cannot push to S3.' sys.exit(1) dest = os.path.basename(source) conn = boto.connect_s3(settings.S3_AUTH['key'], settings.S3_AUTH['secret']) bucket = conn.get_bucket(settings.S3_BUCKET) k = Key(bucket) k.key = dest k.set_contents_from_filename(source) log.debug('Uploaded: {0} to: {1}'.format(source, dest)) class Command(BaseCommand):
from django.core.cache import cache from django.core.exceptions import PermissionDenied from django.core.urlresolvers import reverse from django.views import debug import requests from aesfield.field import AESField from rest_framework.decorators import api_view from rest_framework.response import Response from lib.bango.constants import STATUS_BAD from lib.sellers.models import Seller, SellerProduct from lib.transactions.constants import STATUS_FAILED from solitude.logger import getLogger log = getLogger("s.services") class StatusObject(object): def __init__(self): self.status = {} self.error = None @property def is_proxy(self): return getattr(settings, "SOLITUDE_PROXY", {}) def test_cache(self): # caching fails silently so we have to read from it after writing. cache.set("status", "works") if cache.get("status") == "works":
import requests from curling.lib import sign_request from django_statsd.clients import statsd from lxml import etree from slumber import url_join from lib.bango.constants import HEADERS_SERVICE_GET, HEADERS_WHITELIST_INVERTED from lib.boku.client import get_boku_request_signature from lib.paypal.client import get_client as paypal_client from lib.paypal.constants import HEADERS_URL_GET, HEADERS_TOKEN_GET from lib.paypal.map import urls from solitude.base import dump_request, dump_response from solitude.logger import getLogger log = getLogger('s.proxy') bango_timeout = getattr(settings, 'BANGO_TIMEOUT', 10) def qs_join(**kwargs): return '{url}?{query}'.format(**kwargs) class Proxy(object): # Override this in your proxy class. name = None # Values that we'll populate from the request, optionally. body = None headers = None url = None # Name of settings variables.
from datetime import datetime, timedelta from django import forms from django.conf import settings from django_paranoia.forms import ParanoidForm from lib.transactions import constants from lib.transactions.constants import STATUSES from solitude.base import log_cef from solitude.logger import getLogger log = getLogger('s.transaction') def check_status(old, new): if ((old['created'] + timedelta(seconds=settings.TRANSACTION_LOCKDOWN)) < datetime.now()): raise forms.ValidationError('Transaction locked down') elif old['status'] == constants.STATUS_PENDING: return elif old['status'] in [ constants.STATUS_FAILED, constants.STATUS_ERRORED, constants.STATUS_CANCELLED ]: msg = 'Cannot change state: {0}'.format(old['status']) log.error(msg) raise forms.ValidationError(msg)
import csv import os import tempfile from datetime import datetime, timedelta from optparse import make_option from lib.transactions import constants from lib.transactions.models import Transaction from solitude.logger import getLogger from solitude.management.commands.push_s3 import push from django.core.management.base import BaseCommand, CommandError log = getLogger('s.transactions') def generate_log(day, filename, log_type): out = open(filename, 'w') writer = csv.writer(out) next_day = day + timedelta(days=1) writer.writerow(('version', 'uuid', 'created', 'modified', 'amount', 'currency', 'status', 'buyer', 'seller', 'source', 'carrier', 'region', 'provider')) transactions = Transaction.objects.filter(modified__range=(day, next_day)) if log_type == 'stats': for row in transactions: row.log.get_or_create(type=constants.LOG_STATS) writer.writerow(row.for_log())
from django.http import Http404 from django.test.client import Client from django.utils.decorators import method_decorator from django.views.decorators.http import etag from cef import log_cef as _log_cef from rest_framework import mixins from rest_framework import serializers, status from rest_framework.response import Response from rest_framework.utils.encoders import JSONEncoder from rest_framework.views import APIView from rest_framework.viewsets import GenericViewSet from solitude.logger import getLogger log = getLogger('s') dump_log = getLogger('s.dump') sys_cef_log = getLogger('s.cef') def get_objects(data): # If its a Serializer. if isinstance(data, BaseSerializer): return [data.object] # If its a queryset. if isinstance(data, QuerySet): return data def etag_func(request, data, *args, **kwargs):
from tastypie.authorization import Authorization from tastypie.exceptions import ImmediateHttpResponse, InvalidFilterError from tastypie.fields import ToOneField from tastypie.resources import (ModelResource as TastyPieModelResource, Resource as TastyPieResource) from tastypie.utils import dict_strip_unicode_keys from tastypie.validation import FormValidation import test_utils from lib.delayable.tasks import delayable from solitude.authentication import OAuthAuthentication from solitude.logger import getLogger log = getLogger('s') tasty_log = getLogger('django.request.tastypie') def colorize(colorname, text): if curlish: return get_color(colorname) + text + ANSI_CODES['reset'] return text def formatted_json(json): if curlish: print_formatted_json(json) return print json
from django.db.transaction import set_rollback from rest_framework.response import Response from rest_framework.views import exception_handler from lib.bango.errors import BangoImmediateError from solitude.logger import getLogger log = getLogger('s') def custom_exception_handler(exc): # If you raise an error in solitude, it comes to here and # we rollback the transaction. log.info('Handling exception, about to roll back for: {}, {}'.format( type(exc), exc.message)) set_rollback(True) if hasattr(exc, 'formatter'): try: return Response(exc.formatter(exc).format(), status=getattr(exc, 'status_code', 422)) except: # If the formatter fails, fall back to the standard # error formatting. log.exception('Failed to use formatter.') if isinstance(exc, BangoImmediateError): return Response(exc.message, status=400) return exception_handler(exc)
from tastypie import http from tastypie.authorization import Authorization from tastypie.exceptions import ImmediateHttpResponse, InvalidFilterError from tastypie.fields import ToOneField from tastypie.resources import (ModelResource as TastyPieModelResource, Resource as TastyPieResource, convert_post_to_patch) from tastypie.utils import dict_strip_unicode_keys from tastypie.validation import FormValidation import test_utils from solitude.authentication import OAuthAuthentication from solitude.logger import getLogger log = getLogger('s') sys_cef_log = getLogger('s.cef') tasty_log = getLogger('django.request.tastypie') def etag_func(request, data, *args, **kwargs): if hasattr(request, 'initial_etag'): all_etags = [str(request.initial_etag)] else: if data: try: objects = [data.obj] # Detail case. except AttributeError: try: objects = data['objects'] # List case. except (TypeError, KeyError):
from django.shortcuts import get_object_or_404 from rest_framework import viewsets from rest_framework.exceptions import PermissionDenied from rest_framework.response import Response from lib.provider.serializers import SellerProductReferenceSerializer, SellerReferenceSerializer, TermsSerializer from lib.provider.views import ProxyView from lib.sellers.models import SellerProductReference, SellerReference from solitude.logger import getLogger log = getLogger("s.provider") class MashupView(ProxyView): """ Overrides the normal proxy view to first process the data locally and then remotely, storing data in reference_id fields on the objects. This allows clients interacting with solitude to make one call which hits solitude and the back end server, limiting the amount of knowledge the client has to have about the backend service, such as zippy. """ _proxy_reset = False def post(self, request, *args, **kwargs): serializer = self.serializer_class(data=request.DATA) if serializer.is_valid():
from django.conf import settings from .client import get_client from .errors import PaypalError from solitude.logger import getLogger log = getLogger('s.paypal') class Check(object): """ Run a series of tests on PayPal for either an addon or a paypal_id. The add-on is not required, but we'll do another check or two if the add-on is there. """ def __init__(self, paypal_id=None, token=None, prices=None): # If this state flips to False, it means they need to # go to Paypal and re-set up permissions. We'll assume the best. self.state = {'permissions': True} self.tests = ['id', 'refund', 'currencies'] for test in self.tests: # Three states for pass: # None: haven't tried # False: tried but failed # True: tried and passed self.state[test] = {'pass': None, 'errors': []} self.paypal_id = paypal_id self.paypal_permissions_token = token self.prices = prices
from rest_framework.decorators import api_view from rest_framework.request import Request from rest_framework.response import Response from lib.buyers.field import ConsistentSigField from lib.buyers.forms import PinForm from lib.buyers.models import Buyer from lib.buyers.serializers import ( BuyerSerializer, ConfirmedSerializer, VerifiedSerializer) from solitude.base import log_cef, NonDeleteModelViewSet from solitude.errors import FormError from solitude.filter import StrictQueryFilter from solitude.logger import getLogger log = getLogger('s.buyer') class HashedEmailRequest(Request): @property def QUERY_PARAMS(self): data = self._request.GET.copy() if 'email' in data: email = data.pop('email') if len(email) > 1: raise ValueError('Multiple values of email not supported') data['email_sig'] = ConsistentSigField()._hash(email[0]) return data
# Turn the method into the approiate name. If the Bango WSDL diverges this will # need to change. def get_request(name): return name + 'Request' def get_response(name): return name + 'Response' def get_result(name): return name + 'Result' log = getLogger('s.bango') class Client(object): def __getattr__(self, attr): for name, methods in (['exporter', exporter], ['billing', billing], ['direct', direct], ['token_checker', token_checker]): if attr in methods: return functools.partial(self.call, attr, wsdl=str(name)) raise AttributeError('Unknown request: %s' % attr) def call(self, name, data, wsdl='exporter'): log.info('Bango client call: {0} from wsdl: {1}'.format(name, wsdl)) client = self.client(wsdl) package = client.factory.create(get_request(name))
from rest_framework.response import Response from lib.bango.client import ClientMock from lib.bango.constants import CANT_REFUND, NOT_SUPPORTED, OK, PENDING from lib.bango.errors import BangoAnticipatedError from lib.bango.forms import RefundForm, RefundStatusForm from lib.bango.serializers import EasyObject, RefundSerializer from lib.bango.views.base import BangoResource from lib.transactions.constants import (STATUS_COMPLETED, STATUS_FAILED, STATUS_PENDING, TYPE_REFUND, TYPE_REFUND_MANUAL) from lib.transactions.models import Transaction from solitude.base import NonDeleteModelViewSet from solitude.logger import getLogger log = getLogger('s.bango.refund') class RefundViewSet(NonDeleteModelViewSet, BangoResource): """ A specific resource for creating refunds and then checking the state of that refund against Bango. Since a transaction is created, you can examine the state of the transaction in solitude without having to check against Bango. """ serializer_class = RefundSerializer queryset = Transaction.objects.filter() def update(self): return Response(status=405)
from urlparse import urljoin from django.conf import settings import oauth2 from rest_framework.authentication import BaseAuthentication from rest_framework.exceptions import AuthenticationFailed from tastypie.authentication import Authentication from solitude.logger import getLogger from solitude.middleware import set_oauth_key log = getLogger('s.auth') class Consumer(object): def __init__(self, key, secret=None): self.key = key self.secret = secret or settings.CLIENT_OAUTH_KEYS[key] class OAuthError(RuntimeError): def __init__(self, message='OAuth error occured.'): self.message = message class OAuthAuthentication(Authentication): """ This is based on https://github.com/amrox/django-tastypie-two-legged-oauth with permission.
from tastypie.fields import ToOneField from tastypie.resources import ( ModelResource as TastyPieModelResource, Resource as TastyPieResource, convert_post_to_patch, ) from tastypie.utils import dict_strip_unicode_keys from tastypie.validation import FormValidation import test_utils from solitude.authentication import OAuthAuthentication from solitude.logger import getLogger from solitude.related_fields import PathRelatedField log = getLogger("s") dump_log = getLogger("s.dump") sys_cef_log = getLogger("s.cef") tasty_log = getLogger("django.request.tastypie") def etag_func(request, data, *args, **kwargs): if hasattr(request, "initial_etag"): all_etags = [str(request.initial_etag)] else: if data: try: objects = [data.obj] # Detail case. except AttributeError: try: objects = data["objects"] # List case.
import os import sys from optparse import make_option from django.conf import settings from django.core.management.base import BaseCommand import boto from boto.s3.key import Key from solitude.logger import getLogger log = getLogger('s.s3') def push(source): if not all(settings.S3_AUTH.values() + [settings.S3_BUCKET,]): print 'Settings incomplete, cannot push to S3.' sys.exit(1) dest = os.path.basename(source) conn = boto.connect_s3(settings.S3_AUTH['key'], settings.S3_AUTH['secret']) bucket = conn.get_bucket(settings.S3_BUCKET) k = Key(bucket) k.key = dest k.set_contents_from_filename(source) log.debug('Uploaded: {0} to: {1}'.format(source, dest)) class Command(BaseCommand):
from django.core.cache import cache from django.core.exceptions import PermissionDenied from django.core.urlresolvers import reverse from django.views import debug import requests from aesfield.field import AESField from rest_framework.decorators import api_view from rest_framework.response import Response from lib.bango.constants import STATUS_BAD from lib.sellers.models import Seller, SellerProduct from lib.transactions.constants import STATUS_FAILED from solitude.logger import getLogger log = getLogger('s.services') class StatusObject(object): def __init__(self): self.status = {} self.error = None @property def is_proxy(self): return getattr(settings, 'SOLITUDE_PROXY', {}) def test_cache(self): # caching fails silently so we have to read from it after writing. cache.set('status', 'works')
from datetime import datetime, timedelta from optparse import make_option from django.core.management.base import BaseCommand, CommandError import braintree import requests from braintree.util.crypto import Crypto from lib.brains.management.commands.samples import webhooks from lib.brains.models import BraintreeSubscription from lib.transactions.models import Transaction from payments_config import products from solitude.logger import getLogger log = getLogger('s.brains.management') valid_kinds = [ 'subscription_charged_successfully', 'subscription_charged_unsuccessfully', 'subscription_canceled', ] class Command(BaseCommand): """ This is a crude way to generate and test webhook notifications. It does this by grabbing a sample request from Braintree and then reformatting it with some local data. There are some inherent problems with this: * the XML format might change
from django.conf import settings from django.core.exceptions import ImproperlyConfigured from django.core.urlresolvers import reverse import requests from django_statsd.clients import statsd from lxml import etree from slumber import url_join from curling.lib import sign_request from lib.bango.constants import HEADERS_ALLOWED_INVERTED, HEADERS_SERVICE_GET from lib.proxy.constants import HEADERS_URL_GET from solitude.base import dump_request, dump_response from solitude.logger import getLogger log = getLogger('s.proxy') bango_timeout = getattr(settings, 'BANGO_TIMEOUT', 10) def qs_join(**kwargs): return '{url}?{query}'.format(**kwargs) class Proxy(object): # Override this in your proxy class. name = None # Values that we'll populate from the request, optionally. body = None headers = None url = None # Name of settings variables.
from rest_framework.decorators import api_view from rest_framework.response import Response from lib.brains import serializers from lib.brains.client import get_client from lib.brains.errors import BraintreeResultError from lib.brains.forms import PaymentMethodForm, PayMethodDeleteForm from lib.brains.models import BraintreePaymentMethod from solitude.base import NoAddModelViewSet from solitude.constants import PAYMENT_METHOD_CARD from solitude.errors import FormError from solitude.logger import getLogger log = getLogger('s.brains') @api_view(['POST']) def delete(request): form = PayMethodDeleteForm(request.DATA) if not form.is_valid(): raise FormError(form.errors) solitude_method = form.cleaned_data['paymethod'] solitude_method.braintree_delete() solitude_method.active = False solitude_method.save() log.info('Payment method deleted from braintree: {}' .format(solitude_method.pk))
from urlparse import urljoin from django.conf import settings import oauth2 from rest_framework.authentication import BaseAuthentication from rest_framework.exceptions import AuthenticationFailed from tastypie.authentication import Authentication from solitude.logger import getLogger from solitude.middleware import set_oauth_key log = getLogger('s.auth') class Consumer(object): def __init__(self, key, secret=None): self.key = key self.secret = secret or settings.CLIENT_OAUTH_KEYS[key] class OAuthError(RuntimeError): def __init__(self, message='OAuth error occured.'): self.message = message class OAuthAuthentication(Authentication): """ This is based on https://github.com/amrox/django-tastypie-two-legged-oauth with permission. """
from django.conf import settings from .client import get_client from .errors import PaypalError from solitude.logger import getLogger log = getLogger('s.paypal') class Check(object): """ Run a series of tests on PayPal for either an addon or a paypal_id. The add-on is not required, but we'll do another check or two if the add-on is there. """ def __init__(self, paypal_id=None, token=None, prices=None): # If this state flips to False, it means they need to # go to Paypal and re-set up permissions. We'll assume the best. self.state = {'permissions': True} self.tests = ['id', 'refund', 'currencies'] for test in self.tests: # Three states for pass: # None: haven't tried # False: tried but failed # True: tried and passed self.state[test] = {'pass': None, 'errors': []} self.paypal_id = paypal_id self.paypal_permissions_token = token self.prices = prices self.paypal = get_client()
import base64 from braintree.util.xml_util import XmlUtil from braintree.webhook_notification import WebhookNotification from rest_framework.decorators import api_view from rest_framework.response import Response from lib.brains.client import get_client from lib.brains.forms import WebhookParseForm, WebhookVerifyForm from lib.brains.webhooks import Processor from solitude.errors import FormError from solitude.logger import getLogger log = getLogger('s.brains') debug_log = getLogger('s.webhook') def webhook(request): if request.method.lower() == 'get': return verify(request) return parse(request) @api_view(['POST']) def parse(request): form = WebhookParseForm(request.DATA) if not form.is_valid(): raise FormError(form.errors) # Parse the gateway without doing a validation on this server. # The validation has happened on the solitude-auth server.
import uuid from datetime import datetime from django.conf import settings from django.core.urlresolvers import reverse from django.db import models from django.dispatch import Signal from aesfield.field import AESField from .field import ConsistentSigField, HashField from solitude.base import Model from solitude.logger import getLogger log = getLogger(__name__) ANONYMISED = 'anonymised-uuid:' class Buyer(Model): uuid = models.CharField(max_length=255, db_index=True, unique=True) pin = HashField(blank=True, null=True) pin_confirmed = models.BooleanField(default=False) pin_failures = models.IntegerField(default=0) pin_locked_out = models.DateTimeField(blank=True, null=True) pin_was_locked_out = models.BooleanField(default=False) active = models.BooleanField(default=True, db_index=True) new_pin = HashField(blank=True, null=True) needs_pin_reset = models.BooleanField(default=False) email = AESField(blank=True, null=True, aes_key='buyeremail:key') # Because the email field is encrypted we can't do lookups in mysql on it. # This allows us to use a field for lookups, without exposing anything in
from django.db.transaction import set_rollback from rest_framework.response import Response from rest_framework.views import exception_handler from lib.bango.errors import BangoImmediateError from solitude.logger import getLogger log = getLogger('s') def custom_exception_handler(exc): # If you raise an error in solitude, it comes to here and # we rollback the transaction. log.info('Handling exception, about to roll back for: {}, {}' .format(type(exc), exc.message)) set_rollback(True) if hasattr(exc, 'formatter'): try: return Response(exc.formatter(exc).format(), status=getattr(exc, 'status_code', 422)) except: # If the formatter fails, fall back to the standard # error formatting. log.exception('Failed to use formatter.') if isinstance(exc, BangoImmediateError): return Response(exc.message, status=400)
from django.db import models from django.dispatch import receiver from django_statsd.clients import statsd from lib.bango.signals import create as bango_create from lib.paypal.signals import create as paypal_create from lib.transactions import constants from solitude.base import get_object_or_404, Model from solitude.logger import getLogger log = getLogger('s.transaction') stats_log = getLogger('s.transaction.stats') class Transaction(Model): # In the case of some transactions (e.g. Bango) we don't know the amount # until the transaction reaches a certain stage. amount = models.DecimalField(max_digits=9, decimal_places=2, blank=True, null=True) buyer = models.ForeignKey('buyers.Buyer', blank=True, null=True, db_index=True) currency = models.CharField(max_length=3, blank=True) provider = models.PositiveIntegerField(choices=constants.SOURCES_CHOICES) related = models.ForeignKey('self', blank=True, null=True, on_delete=models.PROTECT, related_name='relations') seller_product = models.ForeignKey('sellers.SellerProduct', db_index=True) status = models.PositiveIntegerField(default=constants.STATUS_DEFAULT, choices=constants.STATUSES_CHOICES)
from rest_framework.filters import DjangoFilterBackend from solitude.errors import InvalidQueryParams from solitude.logger import getLogger log = getLogger('s.filter') class StrictQueryFilter(DjangoFilterBackend): """ Don't allow people to typo request params and return all the objects. Instead limit it down to the parameters allowed in filter_fields. """ def get_filter_class(self, view, queryset=None): klass = (super(StrictQueryFilter, self) .get_filter_class(view, queryset=queryset)) try: # If an ordering exists on the model, use that. klass._meta.order_by = klass.Meta.model.Meta.ordering except AttributeError: pass return klass def filter_queryset(self, request, queryset, view): requested = set(request.QUERY_PARAMS.keys()) allowed = set(getattr(view, 'filter_fields', [])) difference = requested.difference(allowed) if difference: raise InvalidQueryParams(
import uuid from datetime import datetime from django.conf import settings from django.core.urlresolvers import reverse from django.db import models from django.dispatch import Signal from aesfield.field import AESField from .field import HashField from solitude.base import Model from solitude.logger import getLogger log = getLogger(__name__) ANONYMISED = 'anonymised-uuid:' class Buyer(Model): uuid = models.CharField(max_length=255, db_index=True, unique=True) pin = HashField(blank=True, null=True) pin_confirmed = models.BooleanField(default=False) pin_failures = models.IntegerField(default=0) pin_locked_out = models.DateTimeField(blank=True, null=True) pin_was_locked_out = models.BooleanField(default=False) active = models.BooleanField(default=True, db_index=True) new_pin = HashField(blank=True, null=True) needs_pin_reset = models.BooleanField(default=False) email = AESField(blank=True, null=True, aes_key='buyeremail:key') locale = models.CharField(max_length=255, blank=True, null=True) # When this is True it means the buyer was created by some trusted
from tastypie.http import HttpNotFound from cached import SimpleResource from lib.bango.client import ClientMock from lib.bango.constants import CANT_REFUND, NOT_SUPPORTED, OK, PENDING from lib.bango.errors import BangoFormError from lib.bango.forms import RefundForm, RefundStatusForm from lib.transactions.constants import (STATUS_COMPLETED, STATUS_FAILED, STATUS_PENDING, TYPE_REFUND) from lib.transactions.models import Transaction from lib.transactions.resources import TransactionResource from solitude.logger import getLogger log = getLogger('s.bango.refund') class RefundResponse(object): def __init__(self, bango, transaction): self.pk = transaction.pk self.status = bango self.transaction = transaction class BangoResponse(object): def __init__(self, code, message, id): self.responseCode = code self.responseMessage = message
from django.core.cache import cache from django.core.exceptions import PermissionDenied from django.core.urlresolvers import reverse from django.views import debug import requests from aesfield.field import AESField from rest_framework.decorators import api_view from rest_framework.response import Response from lib.bango.constants import STATUS_BAD from lib.sellers.models import Seller, SellerProduct from lib.transactions.constants import STATUS_FAILED from solitude.logger import getLogger log = getLogger('s.services') class StatusObject(object): def __init__(self): self.status = {} self.error = None @property def is_proxy(self): return getattr(settings, 'SOLITUDE_PROXY', {}) def test_cache(self): # caching fails silently so we have to read from it after writing. cache.set('status', 'works') if cache.get('status') == 'works':
from django.http import HttpResponse from rest_framework import viewsets from lib.boku.constants import TRANS_STATUS_FROM_VERIFY_CODE from lib.boku.errors import BokuException from lib.boku.utils import verify from lib.boku.forms import EventForm from lib.transactions.constants import (STATUS_COMPLETED, STATUSES_INVERTED) from solitude.base import BaseAPIView, log_cef from solitude.logger import getLogger log = getLogger('s.boku') class Event(viewsets.ViewSet, BaseAPIView): """ Process a Boku server to server notification. See Boku Technical Documentation for an example of the data being sent in. """ def create(self, request): form = EventForm(request.DATA) param = form.data.get('param') if not form.is_valid(): log.info('Notification invalid: {0}'.format(param)) return self.form_errors([form])
from collections import defaultdict from django.core.exceptions import NON_FIELD_ERRORS from solitude.errors import ErrorFormatter from solitude.logger import getLogger log = getLogger('s.brains') class MockError(Exception): """ An attempt was made to use the mock, without a corresponding entry in the mocks dictionary. """ class BraintreeFormatter(ErrorFormatter): def format(self): errors = defaultdict(list) for error in self.error.result.errors.deep_errors: errors[error.attribute].append({ 'code': error.code, 'message': error.message }) # If there's not a verification object, # there will be a transaction object or neither. error = (self.error.result.credit_card_verification or self.error.result.transaction) if error:
from django_statsd.clients import statsd from cached import Resource from lib.bango.constants import CANCEL, OK from lib.bango.forms import EventForm, NotificationForm from lib.transactions.constants import (STATUS_CANCELLED, STATUS_COMPLETED, STATUS_FAILED) from solitude.logger import getLogger log = getLogger('s.bango') class NotificationResource(Resource): """ Process a Bango notification. See the success URL endpoint in WebPay for an example of the Bango query string. """ class Meta(Resource.Meta): resource_name = 'notification' list_allowed_methods = ['post'] def obj_create(self, bundle, request, **kwargs): form = NotificationForm(bundle.data) bill_conf_id = form.data.get('billing_config_id') log.info('Received notification for billing_config_id %r: ' 'bango_response_code: %r; bango_response_message: %r; ' 'bango_trans_id: %r; moz_transaction: %r; '
import time from collections import OrderedDict from django.core.urlresolvers import reverse from django.db import models from django.dispatch import receiver from django_statsd.clients import statsd from lib.transactions import constants from solitude.base import Model from solitude.logger import getLogger from solitude.utils import shorter log = getLogger('s.transaction') stats_log = getLogger('s.transaction.stats') class Transaction(Model): # In the case of some transactions (e.g. Bango) we don't know the amount # until the transaction reaches a certain stage. amount = models.DecimalField(max_digits=9, decimal_places=2, blank=True, null=True) buyer = models.ForeignKey('buyers.Buyer', blank=True, null=True, db_index=True) # The carrier if this was carrier billing. carrier = models.CharField(max_length=255, blank=True, null=True, db_index=True) currency = models.CharField(max_length=3, blank=True) provider = models.PositiveIntegerField( choices=constants.PROVIDERS_CHOICES, blank=True, null=True)
from django_statsd.clients import statsd from cached import Resource from lib.bango.constants import CANCEL, OK from lib.bango.forms import EventForm, NotificationForm from lib.transactions.constants import STATUS_CANCELLED, STATUS_COMPLETED, STATUS_FAILED, STATUSES_INVERTED from solitude.base import log_cef from solitude.logger import getLogger log = getLogger("s.bango") class NotificationResource(Resource): """ Process a Bango notification. See the success URL endpoint in WebPay for an example of the Bango query string. """ class Meta(Resource.Meta): resource_name = "notification" list_allowed_methods = ["post"] def obj_create(self, bundle, request, **kwargs): form = NotificationForm(request, bundle.data) bill_conf_id = form.data.get("billing_config_id") log.info( "Received notification for billing_config_id %r: " "bango_response_code: %r; bango_response_message: %r; "
from rest_framework.filters import DjangoFilterBackend from solitude.errors import InvalidQueryParams from solitude.logger import getLogger log = getLogger('s.filter') class StrictQueryFilter(DjangoFilterBackend): """ Don't allow people to typo request params and return all the objects. Instead limit it down to the parameters allowed in filter_fields. """ def get_filter_class(self, view, queryset=None): klass = (super(StrictQueryFilter, self).get_filter_class(view, queryset=queryset)) try: # If an ordering exists on the model, use that. klass._meta.order_by = klass.Meta.model.Meta.ordering except AttributeError: pass return klass def filter_queryset(self, request, queryset, view): requested = set(request.QUERY_PARAMS.keys()) allowed = set(getattr(view, 'filter_fields', [])) difference = requested.difference(allowed) if difference: raise InvalidQueryParams(detail='Incorrect query parameters: ' + ','.join(difference))
from lib.boku.constants import CURRENCIES from lib.transactions.constants import (PROVIDER_BOKU, STATUS_COMPLETED) from lib.transactions.models import Transaction from django.conf import settings from django.utils.functional import cached_property from django.utils.translation import ugettext_lazy as _ from lib.boku import constants from lib.boku.client import get_client from lib.boku.errors import BokuException from lib.sellers.models import Seller from solitude.logger import getLogger log = getLogger('s.boku') class BokuForm(forms.Form): """ Boku form fields all have - in them for the field names, which makes them hard to process in a Django form. This converts all the - to _. It does not check if that causes any conflicts. """ def __init__(self, data=None, files=None, **kwargs): data = dict((k.replace('-', '_'), v) for k, v in (data or {}).items()) super(BokuForm, self).__init__(data=data, files=files, **kwargs) class EventForm(BokuForm):
from collections import defaultdict from django.conf import settings from curling.lib import API from solitude.logger import getLogger log = getLogger('s.provider') mock_data = defaultdict(dict) class Client(object): def __init__(self, reference_name): self.config = settings.ZIPPY_CONFIGURATION.get(reference_name) self.api = None if self.config: self.api = API(self.config['url'], append_slash=False) self.api.activate_oauth(self.config['auth']['key'], self.config['auth']['secret'], params={'oauth_token': 'not-implemented'}) else: log.warning('No config for {ref}; oauth disabled' .format(ref=reference_name)) class APIMockObject(object): def __init__(self, resource_name, pk=None): self.resource_name = resource_name self.pk = pk
from django.http import HttpResponse from rest_framework import viewsets from lib.boku.constants import TRANS_STATUS_FROM_VERIFY_CODE from lib.boku.errors import BokuException from lib.boku.utils import verify from lib.boku.forms import EventForm from lib.transactions.constants import STATUS_COMPLETED, STATUSES_INVERTED from solitude.base import BaseAPIView, log_cef from solitude.logger import getLogger log = getLogger("s.boku") class Event(viewsets.ViewSet, BaseAPIView): """ Process a Boku server to server notification. See Boku Technical Documentation for an example of the data being sent in. """ def create(self, request): form = EventForm(request.DATA) param = form.data.get("param") if not form.is_valid(): log.info("Notification invalid: {0}".format(param)) return self.form_errors([form])