205 строки
7.9 KiB
Python
205 строки
7.9 KiB
Python
from django.conf import settings
|
|
from django.conf.urls.defaults import url
|
|
from django.core.exceptions import ObjectDoesNotExist
|
|
|
|
import commonware.log
|
|
import waffle
|
|
from tastypie import fields, http
|
|
from tastypie.exceptions import ImmediateHttpResponse
|
|
from tastypie.validation import CleanedDataFormValidation
|
|
|
|
import amo
|
|
from amo.helpers import absolutify, urlparams
|
|
from amo.urlresolvers import reverse
|
|
from amo.utils import send_mail_jinja
|
|
from constants.payments import PROVIDER_LOOKUP
|
|
from mkt.api.authentication import (OAuthAuthentication,
|
|
OptionalOAuthAuthentication,
|
|
SharedSecretAuthentication)
|
|
from mkt.api.authorization import (AnonymousReadOnlyAuthorization,
|
|
Authorization, OwnerAuthorization,
|
|
PermissionAuthorization)
|
|
from mkt.api.base import (CORSResource, GenericObject, http_error,
|
|
MarketplaceModelResource, MarketplaceResource)
|
|
from mkt.webpay.forms import FailureForm, PrepareForm, ProductIconForm
|
|
from mkt.webpay.models import ProductIcon
|
|
from mkt.purchase.webpay import _prepare_pay
|
|
from market.models import Price, price_locale
|
|
from stats.models import Contribution
|
|
|
|
from . import tasks
|
|
|
|
log = commonware.log.getLogger('z.webpay')
|
|
|
|
|
|
class PreparePayResource(CORSResource, MarketplaceResource):
|
|
webpayJWT = fields.CharField(attribute='webpayJWT', readonly=True)
|
|
contribStatusURL = fields.CharField(attribute='contribStatusURL',
|
|
readonly=True)
|
|
|
|
class Meta(MarketplaceResource.Meta):
|
|
always_return_data = True
|
|
authentication = (SharedSecretAuthentication(), OAuthAuthentication())
|
|
authorization = Authorization()
|
|
detail_allowed_methods = []
|
|
list_allowed_methods = ['post']
|
|
object_class = GenericObject
|
|
resource_name = 'prepare'
|
|
validation = CleanedDataFormValidation(form_class=PrepareForm)
|
|
|
|
def obj_create(self, bundle, request, **kwargs):
|
|
region = getattr(request, 'REGION', None)
|
|
|
|
if region and region.id not in settings.PURCHASE_ENABLED_REGIONS:
|
|
log.info('Region {0} is not in {1}'
|
|
.format(region.id, settings.PURCHASE_ENABLED_REGIONS))
|
|
if not waffle.flag_is_active(request, 'allow-paid-app-search'):
|
|
log.info('Flag not active')
|
|
raise http_error(http.HttpForbidden,
|
|
'Not allowed to purchase for this flag')
|
|
|
|
bundle.obj = GenericObject(_prepare_pay(request, bundle.data['app']))
|
|
return bundle
|
|
|
|
|
|
class StatusPayResource(CORSResource, MarketplaceModelResource):
|
|
|
|
class Meta(MarketplaceModelResource.Meta):
|
|
always_return_data = True
|
|
authentication = (SharedSecretAuthentication(), OAuthAuthentication())
|
|
authorization = OwnerAuthorization()
|
|
detail_allowed_methods = ['get']
|
|
queryset = Contribution.objects.filter(type=amo.CONTRIB_PURCHASE)
|
|
resource_name = 'status'
|
|
|
|
def obj_get(self, request=None, **kw):
|
|
try:
|
|
obj = super(StatusPayResource, self).obj_get(request=request, **kw)
|
|
except ObjectDoesNotExist:
|
|
# Anything that's not correct will be raised as a 404 so that it's
|
|
# harder to iterate over contribution values.
|
|
log.info('Contribution not found')
|
|
return None
|
|
|
|
if not OwnerAuthorization().is_authorized(request, object=obj):
|
|
raise http_error(http.HttpForbidden,
|
|
'You are not an author of that app.')
|
|
|
|
if not obj.addon.has_purchased(request.amo_user):
|
|
log.info('Not in AddonPurchase table')
|
|
return None
|
|
|
|
return obj
|
|
|
|
def base_urls(self):
|
|
return [
|
|
url(r"^(?P<resource_name>%s)/(?P<uuid>[^/]+)/$" %
|
|
self._meta.resource_name,
|
|
self.wrap_view('dispatch_detail'),
|
|
name='api_dispatch_detail')
|
|
]
|
|
|
|
def full_dehydrate(self, bundle):
|
|
bundle.data = {'status': 'complete' if bundle.obj.id else 'incomplete'}
|
|
return bundle
|
|
|
|
|
|
class PriceResource(CORSResource, MarketplaceModelResource):
|
|
prices = fields.ListField(attribute='prices', readonly=True)
|
|
localized = fields.DictField(attribute='suggested', readonly=True,
|
|
blank=True, null=True)
|
|
pricePoint = fields.CharField(attribute='name', readonly=True)
|
|
name = fields.CharField(attribute='tier_name', readonly=True)
|
|
|
|
class Meta:
|
|
detail_allowed_methods = ['get']
|
|
filtering = {'pricePoint': 'exact'}
|
|
include_resource_uri = False
|
|
list_allowed_methods = ['get']
|
|
queryset = Price.objects.filter(active=True).order_by('price')
|
|
resource_name = 'prices'
|
|
|
|
def _get_prices(self, bundle):
|
|
"""Both localized and prices need access to this. """
|
|
provider = bundle.request.GET.get('provider', None)
|
|
if provider:
|
|
provider = PROVIDER_LOOKUP[provider]
|
|
return bundle.obj.prices(provider=provider)
|
|
|
|
def dehydrate_localized(self, bundle):
|
|
region = bundle.request.REGION
|
|
|
|
for price in self._get_prices(bundle):
|
|
if price['region'] == region.id:
|
|
result = price.copy()
|
|
result.update({
|
|
'locale': price_locale(price['price'], price['currency']),
|
|
'region': region.name,
|
|
})
|
|
return result
|
|
|
|
return {}
|
|
|
|
def dehydrate_prices(self, bundle):
|
|
return self._get_prices(bundle)
|
|
|
|
|
|
class FailureNotificationResource(MarketplaceModelResource):
|
|
|
|
class Meta:
|
|
authentication = OAuthAuthentication()
|
|
authorization = PermissionAuthorization('Transaction', 'NotifyFailure')
|
|
detail_allowed_methods = ['patch']
|
|
queryset = Contribution.objects.filter(uuid__isnull=False)
|
|
resource_name = 'failure'
|
|
|
|
def obj_update(self, bundle, **kw):
|
|
form = FailureForm(bundle.data)
|
|
if not form.is_valid():
|
|
raise self.form_errors(form)
|
|
|
|
data = {'transaction_id': bundle.obj,
|
|
'transaction_url': absolutify(
|
|
urlparams(reverse('mkt.developers.transactions'),
|
|
transaction_id=bundle.obj.uuid)),
|
|
'url': form.cleaned_data['url'],
|
|
'retries': form.cleaned_data['attempts']}
|
|
owners = bundle.obj.addon.authors.values_list('email', flat=True)
|
|
send_mail_jinja('Payment notification failure.',
|
|
'webpay/failure.txt',
|
|
data, recipient_list=owners)
|
|
return bundle
|
|
|
|
|
|
class ProductIconResource(CORSResource, MarketplaceModelResource):
|
|
url = fields.CharField(readonly=True)
|
|
|
|
class Meta(MarketplaceResource.Meta):
|
|
authentication = OptionalOAuthAuthentication()
|
|
authorization = AnonymousReadOnlyAuthorization(
|
|
authorizer=PermissionAuthorization('ProductIcon', 'Create'))
|
|
detail_allowed_methods = ['get']
|
|
fields = ['ext_url', 'ext_size', 'size']
|
|
filtering = {
|
|
'ext_url': 'exact',
|
|
'ext_size': 'exact',
|
|
'size': 'exact',
|
|
}
|
|
list_allowed_methods = ['get', 'post']
|
|
queryset = ProductIcon.objects.filter()
|
|
resource_name = 'product/icon'
|
|
validation = CleanedDataFormValidation(form_class=ProductIconForm)
|
|
|
|
def dehydrate_url(self, bundle):
|
|
return bundle.obj.url()
|
|
|
|
def obj_create(self, bundle, request, **kwargs):
|
|
log.info('Resizing product icon %s @ %s to %s for webpay'
|
|
% (bundle.data['ext_url'], bundle.data['ext_size'],
|
|
bundle.data['size']))
|
|
tasks.fetch_product_icon.delay(bundle.data['ext_url'],
|
|
bundle.data['ext_size'],
|
|
bundle.data['size'])
|
|
# Tell the client that deferred processing will create an object.
|
|
raise ImmediateHttpResponse(response=http.HttpAccepted())
|