def paypal_completed(request, transaction_id, serialize=None, amount=None): # Make sure transaction has not yet been processed. if Contribution.objects.filter(transaction_id=transaction_id).exists(): paypal_log.info('Completed IPN already processed') return http.HttpResponse('Transaction already processed') # Note that when this completes the uuid is moved over to transaction_id. try: original = Contribution.objects.get(uuid=transaction_id) except Contribution.DoesNotExist: paypal_log.info('Ignoring transaction: %s' % transaction_id) return http.HttpResponse('Transaction not found; skipping.') paypal_log.info('Completed IPN received: %s' % transaction_id) data = StatsDictField().to_python(php.serialize(serialize)) update = {'transaction_id': transaction_id, 'uuid': None, 'post_data': data} if amount: update['amount'] = _parse_currency(amount)['amount'] original.update(**update) # Send thankyou email. try: original.mail_thankyou(request) except ContributionError as e: # A failed thankyou email is not a show stopper, but is good to know. paypal_log.error('Thankyou note email failed with error: %s' % e) paypal_log_cef(request, original.addon, transaction_id, 'Contribution', 'CONTRIBUTION', 'A user contributed to an addon') paypal_log.info('Completed successfully processed') return http.HttpResponse('Success!')
def paypal_reversal(request, post, transaction): try: original = Contribution.objects.get(transaction_id=post['txn_id']) except Contribution.DoesNotExist: return _log_unmatched(post) # If the contribution has a related contribution we've processed it. try: original = Contribution.objects.get(related=original) paypal_log.info('Related contribution, state: %s, pk: %s' % (original.related.type, original.related.pk)) return http.HttpResponse('Transaction already processed') except Contribution.DoesNotExist: pass paypal_log.info('Reversal IPN received: %s' % post['txn_id']) amount = _parse_currency(transaction['amount']) refund = Contribution.objects.create( addon=original.addon, related=original, user=original.user, type=amo.CONTRIB_CHARGEBACK, amount=-amount['amount'], currency=amount['currency'], post_data=php.serialize(post) ) refund.mail_chargeback() paypal_log_cef(request, original.addon, post['txn_id'], 'Chargeback', 'CHARGEBACK', 'A paypal chargeback was processed') return http.HttpResponse('Success!')
def paypal_refunded(request, post, transaction): try: original = Contribution.objects.get(transaction_id=post['txn_id']) except Contribution.DoesNotExist: return _log_unmatched(post) # If the contribution has a related contribution we've processed it. try: original = Contribution.objects.get(related=original) paypal_log.info('Related contribution, state: %s, pk: %s' % (original.related.type, original.related.pk)) return http.HttpResponse('Transaction already processed') except Contribution.DoesNotExist: pass original.handle_chargeback('refund') paypal_log.info('Refund IPN received: %s' % post['txn_id']) amount = _parse_currency(transaction['amount']) Contribution.objects.create(addon=original.addon, related=original, user=original.user, type=amo.CONTRIB_REFUND, amount=-amount['amount'], currency=amount['currency'], post_data=php.serialize(post)) paypal_log.info('Refund successfully processed') paypal_log_cef(request, original.addon, post['txn_id'], 'Refund', 'REFUND', 'A paypal refund was processed') return http.HttpResponse('Success!')
def paypal_reversal(request, transaction_id, serialize=None, amount=None): try: original = Contribution.objects.get(transaction_id=transaction_id) except Contribution.DoesNotExist: paypal_log.info('Ignoring transaction: %s' % transaction_id) return http.HttpResponse('Transaction not found; skipping.') # If the contribution has a related contribution we've processed it. try: original = Contribution.objects.get(related=original) paypal_log.info('Related contribution, state: %s, pk: %s' % (original.related.type, original.related.pk)) return http.HttpResponse('Transaction already processed') except Contribution.DoesNotExist: pass original.handle_chargeback('reversal') paypal_log.info('Reversal IPN received: %s' % transaction_id) amount = _parse_currency(amount) refund = Contribution.objects.create(addon=original.addon, related=original, user=original.user, type=amo.CONTRIB_CHARGEBACK, amount=-amount['amount'], currency=amount['currency'], post_data=php.serialize(serialize)) refund.mail_chargeback() paypal_log_cef(request, original.addon, transaction_id, 'Chargeback', 'CHARGEBACK', 'A paypal chargeback was processed') return http.HttpResponse('Success!')
def paypal_reversal(request, transaction_id, serialize=None, amount=None): try: original = Contribution.objects.get(transaction_id=transaction_id) except Contribution.DoesNotExist: paypal_log.info('Ignoring transaction: %s' % transaction_id) return http.HttpResponse('Transaction not found; skipping.') # If the contribution has a related contribution we've processed it. try: original = Contribution.objects.get(related=original) paypal_log.info('Related contribution, state: %s, pk: %s' % (original.related.type, original.related.pk)) return http.HttpResponse('Transaction already processed') except Contribution.DoesNotExist: pass original.handle_chargeback('reversal') paypal_log.info('Reversal IPN received: %s' % transaction_id) amount = _parse_currency(amount) refund = Contribution.objects.create( addon=original.addon, related=original, user=original.user, type=amo.CONTRIB_CHARGEBACK, amount=-amount['amount'], currency=amount['currency'], post_data=php.serialize(serialize) ) refund.mail_chargeback() paypal_log_cef(request, original.addon, transaction_id, 'Chargeback', 'CHARGEBACK', 'A paypal chargeback was processed') return http.HttpResponse('Success!')
def paypal_refunded(request, post, transaction): try: original = Contribution.objects.get(transaction_id=post['txn_id']) except Contribution.DoesNotExist: return None # If the contribution has a related contribution we've processed it. try: original = Contribution.objects.get(related=original) paypal_log.info('Related contribution, state: %s, pk: %s' % (original.related.type, original.related.pk)) return http.HttpResponse('Transaction already processed') except Contribution.DoesNotExist: pass paypal_log.info('Refund IPN received: %s' % post['txn_id']) amount = _parse_currency(transaction['amount']) Contribution.objects.create( addon=original.addon, related=original, user=original.user, type=amo.CONTRIB_REFUND, amount=-amount['amount'], currency=amount['currency'], post_data=php.serialize(post) ) paypal_log.info('Refund successfully processed') paypal_log_cef(request, original.addon, post['txn_id'], 'Refund', 'REFUND', 'A paypal refund was processed') return http.HttpResponse('Success!')
def paypal_reversal(request, post, transaction): try: original = Contribution.objects.get(transaction_id=post['txn_id']) except Contribution.DoesNotExist: return None # If the contribution has a related contribution we've processed it. try: original = Contribution.objects.get(related=original) paypal_log.info('Related contribution, state: %s, pk: %s' % (original.related.type, original.related.pk)) return http.HttpResponse('Transaction already processed') except Contribution.DoesNotExist: pass paypal_log.info('Reversal IPN received: %s' % post['txn_id']) amount = _parse_currency(transaction['amount']) refund = Contribution.objects.create(addon=original.addon, related=original, user=original.user, type=amo.CONTRIB_CHARGEBACK, amount=-amount['amount'], currency=amount['currency'], post_data=php.serialize(post)) refund.mail_chargeback() paypal_log_cef(request, original.addon, post['txn_id'], 'Chargeback', 'CHARGEBACK', 'A paypal chargeback was processed') return http.HttpResponse('Success!')
def purchase_complete(request, addon, status): result = '' if status == 'complete': uuid_ = request.GET.get('uuid') log.debug('Looking up contrib for uuid: %s' % uuid_) # The IPN may, or may not have come through. Which means looking for # a for pre or post IPN contributions. If both fail, then we've not # got a matching contribution. lookup = (Q(uuid=uuid_, type=amo.CONTRIB_PENDING) | Q(transaction_id=uuid_, type=amo.CONTRIB_PURCHASE)) con = get_object_or_404(Contribution, lookup) log.debug('Check purchase paypal addon: %s, user: %s, paykey: %s' % (addon.pk, request.amo_user.pk, con.paykey[:10])) try: result = paypal.check_purchase(con.paykey) if result == 'ERROR': paypal.paypal_log_cef( request, addon, uuid_, 'Purchase Fail', 'PURCHASEFAIL', 'Checking purchase state returned error') raise except: paypal.paypal_log_cef( request, addon, uuid_, 'Purchase Fail', 'PURCHASEFAIL', 'There was an error checking purchase state') log.error('Check purchase paypal addon: %s, user: %s, paykey: %s' % (addon.pk, request.amo_user.pk, con.paykey[:10]), exc_info=True) result = 'ERROR' status = 'error' log.debug('Paypal returned: %s for paykey: %s' % (result, con.paykey[:10])) if result == 'COMPLETED' and con.type == amo.CONTRIB_PENDING: con.update(type=amo.CONTRIB_PURCHASE) context = { 'realurl': request.GET.get('realurl', ''), 'status': status, 'result': result } # For mobile, bounce back to the details page. if request.MOBILE: url = urlparams(shared_url('detail', addon), **context) return http.HttpResponseRedirect(url) context.update({'addon': addon}) response = jingo.render(request, 'addons/paypal_result.html', context) response['x-frame-options'] = 'allow' return response
def purchase_complete(request, addon, status): result = "" if status == "complete": uuid_ = request.GET.get("uuid") log.debug("Looking up contrib for uuid: %s" % uuid_) # The IPN may, or may not have come through. Which means looking for # a for pre or post IPN contributions. If both fail, then we've not # got a matching contribution. lookup = Q(uuid=uuid_, type=amo.CONTRIB_PENDING) | Q(transaction_id=uuid_, type=amo.CONTRIB_PURCHASE) con = get_object_or_404(Contribution, lookup) log.debug( "Check purchase paypal addon: %s, user: %s, paykey: %s" % (addon.pk, request.amo_user.pk, con.paykey[:10]) ) try: result = paypal.check_purchase(con.paykey) if result == "ERROR": paypal.paypal_log_cef( request, addon, uuid_, "Purchase Fail", "PURCHASEFAIL", "Checking purchase state returned error" ) raise except: paypal.paypal_log_cef( request, addon, uuid_, "Purchase Fail", "PURCHASEFAIL", "There was an error checking purchase state" ) log.error( "Check purchase paypal addon: %s, user: %s, paykey: %s" % (addon.pk, request.amo_user.pk, con.paykey[:10]), exc_info=True, ) result = "ERROR" status = "error" log.debug("Paypal returned: %s for paykey: %s" % (result, con.paykey[:10])) if result == "COMPLETED" and con.type == amo.CONTRIB_PENDING: con.update(type=amo.CONTRIB_PURCHASE) response = jingo.render( request, "addons/paypal_result.html", { "addon": addon, "realurl": request.GET.get("realurl", ""), # What the client claimed they did. "status": status, # And what paypal thought of that claim. "result": result, }, ) response["x-frame-options"] = "allow" return response
def purchase_complete(request, addon, status): result = '' if status == 'complete': uuid_ = request.GET.get('uuid') log.debug('Looking up contrib for uuid: %s' % uuid_) # The IPN may, or may not have come through. Which means looking for # a for pre or post IPN contributions. If both fail, then we've not # got a matching contribution. lookup = (Q(uuid=uuid_, type=amo.CONTRIB_PENDING) | Q(transaction_id=uuid_, type=amo.CONTRIB_PURCHASE)) con = get_object_or_404(Contribution, lookup) log.debug('Check purchase paypal addon: %s, user: %s, paykey: %s' % (addon.pk, request.amo_user.pk, con.paykey[:10])) try: result = paypal.check_purchase(con.paykey) if result == 'ERROR': paypal.paypal_log_cef( request, addon, uuid_, 'Purchase Fail', 'PURCHASEFAIL', 'Checking purchase state returned error') raise except: paypal.paypal_log_cef( request, addon, uuid_, 'Purchase Fail', 'PURCHASEFAIL', 'There was an error checking purchase state') log.error('Check purchase paypal addon: %s, user: %s, paykey: %s' % (addon.pk, request.amo_user.pk, con.paykey[:10]), exc_info=True) result = 'ERROR' status = 'error' log.debug('Paypal returned: %s for paykey: %s' % (result, con.paykey[:10])) if result == 'COMPLETED' and con.type == amo.CONTRIB_PENDING: con.update(type=amo.CONTRIB_PURCHASE) response = jingo.render( request, 'addons/paypal_result.html', { 'addon': addon, 'realurl': request.GET.get('realurl', ''), # What the client claimed they did. 'status': status, # And what paypal thought of that claim. 'result': result }) response['x-frame-options'] = 'allow' return response
def purchase_complete(request, addon, status): result = "" if status == "complete": uuid_ = request.GET.get("uuid") log.debug("Looking up contrib for uuid: %s" % uuid_) # The IPN may, or may not have come through. Which means looking for # a for pre or post IPN contributions. If both fail, then we've not # got a matching contribution. lookup = Q(uuid=uuid_, type=amo.CONTRIB_PENDING) | Q(transaction_id=uuid_, type=amo.CONTRIB_PURCHASE) con = get_object_or_404(Contribution, lookup) log.debug( "Check purchase paypal addon: %s, user: %s, paykey: %s" % (addon.pk, request.amo_user.pk, con.paykey[:10]) ) try: result = paypal.check_purchase(con.paykey) if result == "ERROR": paypal.paypal_log_cef( request, addon, uuid_, "Purchase Fail", "PURCHASEFAIL", "Checking purchase state returned error" ) raise except: paypal.paypal_log_cef( request, addon, uuid_, "Purchase Fail", "PURCHASEFAIL", "There was an error checking purchase state" ) log.error( "Check purchase paypal addon: %s, user: %s, paykey: %s" % (addon.pk, request.amo_user.pk, con.paykey[:10]), exc_info=True, ) result = "ERROR" status = "error" log.debug("Paypal returned: %s for paykey: %s" % (result, con.paykey[:10])) if result == "COMPLETED" and con.type == amo.CONTRIB_PENDING: con.update(type=amo.CONTRIB_PURCHASE) context = {"realurl": request.GET.get("realurl", ""), "status": status, "result": result} # For mobile, bounce back to the details page. if request.MOBILE: url = urlparams(shared_url("detail", addon), **context) return redirect(url) context.update({"addon": addon}) response = jingo.render(request, "addons/paypal_result.html", context) response["x-frame-options"] = "allow" return response
def purchase_complete(request, addon, status): result = '' if status == 'complete': uuid_ = request.GET.get('uuid') log.debug('Looking up contrib for uuid: %s' % uuid_) # The IPN may, or may not have come through. Which means looking for # a for pre or post IPN contributions. If both fail, then we've not # got a matching contribution. lookup = (Q(uuid=uuid_, type=amo.CONTRIB_PENDING) | Q(transaction_id=uuid_, type=amo.CONTRIB_PURCHASE)) con = get_object_or_404(Contribution, lookup) log.debug('Check purchase paypal addon: %s, user: %s, paykey: %s' % (addon.pk, request.amo_user.pk, con.paykey[:10])) try: result = paypal.check_purchase(con.paykey) if result == 'ERROR': paypal.paypal_log_cef(request, addon, uuid_, 'Purchase Fail', 'PURCHASEFAIL', 'Checking purchase state returned error') raise except: paypal.paypal_log_cef(request, addon, uuid_, 'Purchase Fail', 'PURCHASEFAIL', 'There was an error checking purchase state') log.error('Check purchase paypal addon: %s, user: %s, paykey: %s' % (addon.pk, request.amo_user.pk, con.paykey[:10]), exc_info=True) result = 'ERROR' status = 'error' log.debug('Paypal returned: %s for paykey: %s' % (result, con.paykey[:10])) if result == 'COMPLETED' and con.type == amo.CONTRIB_PENDING: con.update(type=amo.CONTRIB_PURCHASE) context = {'realurl': request.GET.get('realurl', ''), 'status': status, 'result': result} # For mobile, bounce back to the details page. if request.MOBILE: url = urlparams(shared_url('detail', addon), **context) return http.HttpResponseRedirect(url) context.update({'addon': addon}) response = jingo.render(request, 'addons/paypal_result.html', context) response['x-frame-options'] = 'allow' return response
def purchase_complete(request, addon, status): result = '' if status == 'complete': uuid_ = request.GET.get('uuid') log.debug('Looking up contrib for uuid: %s' % uuid_) # The IPN may, or may not have come through. Which means looking for # a for pre or post IPN contributions. If both fail, then we've not # got a matching contribution. lookup = (Q(uuid=uuid_, type=amo.CONTRIB_PENDING) | Q(transaction_id=uuid_, type=amo.CONTRIB_PURCHASE)) con = get_object_or_404(Contribution, lookup) log.debug('Check purchase paypal addon: %s, user: %s, paykey: %s' % (addon.pk, request.amo_user.pk, con.paykey[:10])) try: result = paypal.check_purchase(con.paykey) if result == 'ERROR': paypal.paypal_log_cef(request, addon, uuid_, 'Purchase Fail', 'PURCHASEFAIL', 'Checking purchase state returned error') raise except: paypal.paypal_log_cef(request, addon, uuid_, 'Purchase Fail', 'PURCHASEFAIL', 'There was an error checking purchase state') log.error('Check purchase paypal addon: %s, user: %s, paykey: %s' % (addon.pk, request.amo_user.pk, con.paykey[:10]), exc_info=True) result = 'ERROR' status = 'error' log.debug('Paypal returned: %s for paykey: %s' % (result, con.paykey[:10])) if result == 'COMPLETED' and con.type == amo.CONTRIB_PENDING: con.update(type=amo.CONTRIB_PURCHASE) response = jingo.render(request, 'addons/paypal_result.html', {'addon': addon, 'realurl': request.GET.get('realurl', ''), # What the client claimed they did. 'status': status, # And what paypal thought of that claim. 'result': result}) response['x-frame-options'] = 'allow' return response
def paypal_completed(request, transaction_id, serialize=None, amount=None): # Make sure transaction has not yet been processed. if Contribution.objects.filter(transaction_id=transaction_id).exists(): paypal_log.info('Completed IPN already processed') return http.HttpResponse('Transaction already processed') # Note that when this completes the uuid is moved over to transaction_id. try: original = Contribution.objects.get(uuid=transaction_id) except Contribution.DoesNotExist: paypal_log.info('Ignoring transaction: %s' % transaction_id) return http.HttpResponse('Transaction not found; skipping.') paypal_log.info('Completed IPN received: %s' % transaction_id) data = StatsDictField().to_python(php.serialize(serialize)) update = { 'transaction_id': transaction_id, 'uuid': None, 'post_data': data } if original.type == amo.CONTRIB_PENDING: # This is a purchase that has failed to hit the completed page. # But this ok, this IPN means that it all went through. update['type'] = amo.CONTRIB_PURCHASE # If they failed to hit the completed page, they also failed # to get it logged properly. This will add the log in. amo.log(amo.LOG.PURCHASE_ADDON, original.addon) if amount: update['amount'] = _parse_currency(amount)['amount'] original.update(**update) # Send thankyou email. try: original.mail_thankyou(request) except ContributionError as e: # A failed thankyou email is not a show stopper, but is good to know. paypal_log.error('Thankyou note email failed with error: %s' % e) paypal_log_cef(request, original.addon, transaction_id, 'Purchase', 'PURCHASE', 'A user purchased or contributed to an addon') paypal_log.info('Completed successfully processed') return http.HttpResponse('Success!')
def paypal_completed(request, transaction_id, serialize=None, amount=None): # Make sure transaction has not yet been processed. if Contribution.objects.filter(transaction_id=transaction_id).exists(): paypal_log.info('Completed IPN already processed') return http.HttpResponse('Transaction already processed') # Note that when this completes the uuid is moved over to transaction_id. try: original = Contribution.objects.get(uuid=transaction_id) except Contribution.DoesNotExist: paypal_log.info('Ignoring transaction: %s' % transaction_id) return http.HttpResponse('Transaction not found; skipping.') paypal_log.info('Completed IPN received: %s' % transaction_id) data = StatsDictField().to_python(php.serialize(serialize)) update = {'transaction_id': transaction_id, 'uuid': None, 'post_data': data} if original.type == amo.CONTRIB_PENDING: # This is a purchase that has failed to hit the completed page. # But this ok, this IPN means that it all went through. update['type'] = amo.CONTRIB_PURCHASE # If they failed to hit the completed page, they also failed # to get it logged properly. This will add the log in. amo.log(amo.LOG.PURCHASE_ADDON, original.addon) if amount: update['amount'] = _parse_currency(amount)['amount'] original.update(**update) # Send thankyou email. try: original.mail_thankyou(request) except ContributionError as e: # A failed thankyou email is not a show stopper, but is good to know. paypal_log.error('Thankyou note email failed with error: %s' % e) paypal_log_cef(request, original.addon, transaction_id, 'Purchase', 'PURCHASE', 'A user purchased or contributed to an addon') paypal_log.info('Completed successfully processed') return http.HttpResponse('Success!')
def pay(request, signed_req, pay_req): amount = pay_req['request']['price'] currency = pay_req['request']['currency'] source = request.POST.get('source', '') product = pay_req['_config'].addon # L10n: {0} is the product name. {1} is the application name. contrib_for = ( _(u'Mozilla Marketplace in-app payment for {0} to {1}').format( pay_req['request']['name'], product.name)) uuid_ = hashlib.md5(str(uuid.uuid4())).hexdigest() paykey, status = '', '' preapproval = None if request.amo_user: preapproval = request.amo_user.get_preapproval() try: paykey, status = paypal.get_paykey( dict( amount=amount, chains=settings.PAYPAL_CHAINS, currency=currency, email=product.paypal_id, ip=request.META.get('REMOTE_ADDR'), memo=contrib_for, pattern='inapp_pay.pay_status', preapproval=preapproval, qs={'realurl': request.POST.get('realurl')}, slug=pay_req['_config']. pk, # passed to pay_done() via reverse() uuid=uuid_)) except paypal.PaypalError, exc: paypal.paypal_log_cef(request, product, uuid_, 'in-app PayKey Failure', 'PAYKEYFAIL', 'There was an error getting the paykey') log.error(u'Error getting paykey, in-app payment: %s' % pay_req['_config'].pk, exc_info=True) InappPayLog.log(request, 'PAY_ERROR', config=pay_req['_config']) return render_error(request, exc)
def paypal_completed(request, post, transaction): # Make sure transaction has not yet been processed. if Contribution.objects.filter(transaction_id=post['txn_id']).exists(): paypal_log.info('Completed IPN already processed') return http.HttpResponse('Transaction already processed') # Note that when this completes the uuid is moved over to transaction_id. try: original = Contribution.objects.get(uuid=post['txn_id']) except Contribution.DoesNotExist: return None paypal_log.info('Completed IPN received: %s' % post['txn_id']) data = StatsDictField().to_python(php.serialize(post)) update = { 'transaction_id': post['txn_id'], 'uuid': None, 'post_data': data } if original.type == amo.CONTRIB_PENDING: # This is a purchase that has failed to hit the completed page. # But this ok, this IPN means that it all went through. update['type'] = amo.CONTRIB_PURCHASE if 'mc_gross' in post: update['amount'] = post['mc_gross'] original.update(**update) # Send thankyou email. try: original.mail_thankyou(request) except ContributionError as e: # A failed thankyou email is not a show stopper, but is good to know. paypal_log.error('Thankyou note email failed with error: %s' % e) paypal_log_cef(request, original.addon, post['txn_id'], 'Purchase', 'PURCHASE', 'A user purchased or contributed to an addon') paypal_log.info('Completed successfully processed') return http.HttpResponse('Success!')
def pay(request, signed_req, pay_req): amount = pay_req["request"]["price"] currency = pay_req["request"]["currency"] source = request.POST.get("source", "") addon = pay_req["_config"].addon # L10n: {0} is the product name. {1} is the application name contrib_for = _(u"In-app payment for {0} to {1}").format( jinja2.escape(pay_req["request"]["name"]), jinja2.escape(addon.name) ) uuid_ = hashlib.md5(str(uuid.uuid4())).hexdigest() paykey, status = "", "" preapproval = None if waffle.flag_is_active(request, "allow-pre-auth") and request.amo_user: preapproval = request.amo_user.get_preapproval() try: paykey, status = paypal.get_paykey( dict( amount=amount, chains=settings.PAYPAL_CHAINS, currency=currency, email=addon.paypal_id, ip=request.META.get("REMOTE_ADDR"), memo=contrib_for, pattern="inapp_pay.pay_done", preapproval=preapproval, qs={"realurl": request.POST.get("realurl")}, slug=pay_req["_config"].pk, # passed to pay_done() via reverse() uuid=uuid_, ) ) except paypal.PaypalError, exc: paypal.paypal_log_cef( request, addon, uuid_, "in-app PayKey Failure", "PAYKEYFAIL", "There was an error getting the paykey" ) log.error(u"Error getting paykey, in-app payment: %s" % pay_req["_config"].pk, exc_info=True) InappPayLog.log(request, "PAY_ERROR", config=pay_req["_config"]) return render_error(request, exc)
def pay(request, signed_req, pay_req): paykey, status = '', '' preapproval = None if request.amo_user: preapproval = request.amo_user.get_preapproval() tier, price, currency = _get_price(pay_req, preapproval=preapproval) source = request.POST.get('source', '') product = pay_req['_config'].addon # L10n: {0} is the product name. {1} is the application name. contrib_for = (_(u'Mozilla Marketplace in-app payment for {0} to {1}') .format(pay_req['request']['name'], product.name)) uuid_ = hashlib.md5(str(uuid.uuid4())).hexdigest() try: paykey, status = paypal.get_paykey(dict( amount=price, chains=settings.PAYPAL_CHAINS, currency=currency, email=product.paypal_id, ip=request.META.get('REMOTE_ADDR'), memo=contrib_for, pattern='inapp_pay.pay_status', preapproval=preapproval, qs={'realurl': request.POST.get('realurl')}, slug=pay_req['_config'].pk, # passed to pay_done() via reverse() uuid=uuid_ )) except paypal.PaypalError, exc: paypal.paypal_log_cef(request, product, uuid_, 'in-app PayKey Failure', 'PAYKEYFAIL', 'There was an error getting the paykey') log.error(u'Error getting paykey, in-app payment: %s' % pay_req['_config'].pk, exc_info=True) InappPayLog.log(request, 'PAY_ERROR', config=pay_req['_config']) return render_error(request, exc)
def paypal_completed(request, post, transaction): # Make sure transaction has not yet been processed. if Contribution.objects.filter(transaction_id=post['txn_id']).exists(): paypal_log.info('Completed IPN already processed') return http.HttpResponse('Transaction already processed') # Note that when this completes the uuid is moved over to transaction_id. try: original = Contribution.objects.get(uuid=post['txn_id']) except Contribution.DoesNotExist: return _log_unmatched(post) paypal_log.info('Completed IPN received: %s' % post['txn_id']) data = StatsDictField().to_python(php.serialize(post)) update = {'transaction_id': post['txn_id'], 'uuid': None, 'post_data': data} if original.type == amo.CONTRIB_PENDING: # This is a purchase that has failed to hit the completed page. # But this ok, this IPN means that it all went through. update['type'] = amo.CONTRIB_PURCHASE if 'mc_gross' in post: update['amount'] = post['mc_gross'] original.update(**update) # Send thankyou email. try: original.mail_thankyou(request) except ContributionError as e: # A failed thankyou email is not a show stopper, but is good to know. paypal_log.error('Thankyou note email failed with error: %s' % e) paypal_log_cef(request, original.addon, post['txn_id'], 'Purchase', 'PURCHASE', 'A user purchased or contributed to an addon') paypal_log.info('Completed successfully processed') return http.HttpResponse('Success!')
def paypal_refunded(request, transaction_id, serialize=None, amount=None): try: original = Contribution.objects.get(transaction_id=transaction_id) except Contribution.DoesNotExist: paypal_log.info('Ignoring transaction: %s' % transaction_id) return http.HttpResponse('Transaction not found; skipping.') # If the contribution has a related contribution we've processed it. try: original = Contribution.objects.get(related=original) paypal_log.info('Related contribution, state: %s, pk: %s' % (original.related.type, original.related.pk)) return http.HttpResponse('Transaction already processed') except Contribution.DoesNotExist: pass original.handle_chargeback('refund') paypal_log.info('Refund IPN received: %s' % transaction_id) price_currency = _parse_currency(amount) amount = price_currency['amount'] currency = price_currency['currency'] # Contribution with negative amount for refunds. Contribution.objects.create( addon=original.addon, related=original, user=original.user, type=amo.CONTRIB_REFUND, amount=-amount, currency=currency, price_tier=original.price_tier, post_data=php.serialize(serialize) ) paypal_log.info('Refund successfully processed') paypal_log_cef(request, original.addon, transaction_id, 'Refund', 'REFUND', 'A paypal refund was processed') return http.HttpResponse('Success!')
def paypal_refunded(request, transaction_id, serialize=None, amount=None): try: original = Contribution.objects.get(transaction_id=transaction_id) except Contribution.DoesNotExist: paypal_log.info('Ignoring transaction: %s' % transaction_id) return http.HttpResponse('Transaction not found; skipping.') # If the contribution has a related contribution we've processed it. try: original = Contribution.objects.get(related=original) paypal_log.info('Related contribution, state: %s, pk: %s' % (original.related.type, original.related.pk)) return http.HttpResponse('Transaction already processed') except Contribution.DoesNotExist: pass original.handle_chargeback('refund') paypal_log.info('Refund IPN received: %s' % transaction_id) price_currency = _parse_currency(amount) amount = price_currency['amount'] currency = price_currency['currency'] # Contribution with negative amount for refunds. Contribution.objects.create(addon=original.addon, related=original, user=original.user, type=amo.CONTRIB_REFUND, amount=-amount, currency=currency, price_tier=original.price_tier, post_data=php.serialize(serialize)) paypal_log.info('Refund successfully processed') paypal_log_cef(request, original.addon, transaction_id, 'Refund', 'REFUND', 'A paypal refund was processed') return http.HttpResponse('Success!')
def paypal_completed(request, transaction_id, serialize=None, amount=None): # Make sure transaction has not yet been processed. if Contribution.objects.filter(transaction_id=transaction_id).exists(): paypal_log.info('Completed IPN already processed') return http.HttpResponse('Transaction already processed') # Note that when this completes the uuid is moved over to transaction_id. try: original = Contribution.objects.get(uuid=transaction_id) except Contribution.DoesNotExist: paypal_log.info('Ignoring transaction: %s' % transaction_id) return http.HttpResponse('Transaction not found; skipping.') paypal_log.info('Completed IPN received: %s' % transaction_id) data = StatsDictField().to_python(php.serialize(serialize)) update = { 'transaction_id': transaction_id, 'uuid': None, 'post_data': data } if amount: update['amount'] = _parse_currency(amount)['amount'] original.update(**update) # Send thankyou email. try: original.mail_thankyou(request) except ContributionError as e: # A failed thankyou email is not a show stopper, but is good to know. paypal_log.error('Thankyou note email failed with error: %s' % e) paypal_log_cef(request, original.addon, transaction_id, 'Contribution', 'CONTRIBUTION', 'A user contributed to an addon') paypal_log.info('Completed successfully processed') return http.HttpResponse('Success!')
def purchase(request, addon): log.debug('Starting purchase of addon: %s by user: %s' % (addon.pk, request.amo_user.pk)) amount = addon.premium.get_price() source = request.POST.get('source', '') uuid_ = hashlib.md5(str(uuid.uuid4())).hexdigest() # l10n: {0} is the addon name contrib_for = _(u'Purchase of {0}').format(jinja2.escape(addon.name)) # Default is USD. amount, currency = addon.premium.get_price(), 'USD' # If tier is specified, then let's look it up. form = PriceCurrencyForm(data=request.POST, addon=addon) if form.is_valid(): tier = form.get_tier() if tier: amount, currency = tier.price, tier.currency paykey, status, error = '', '', '' preapproval = None if waffle.flag_is_active(request, 'allow-pre-auth') and request.amo_user: preapproval = request.amo_user.get_preapproval() try: pattern = 'addons.purchase.finished' slug = addon.slug if addon.is_webapp(): pattern = 'apps.purchase.finished' slug = addon.app_slug paykey, status = paypal.get_paykey( dict(amount=amount, chains=settings.PAYPAL_CHAINS, currency=currency, email=addon.paypal_id, ip=request.META.get('REMOTE_ADDR'), memo=contrib_for, pattern=pattern, preapproval=preapproval, qs={'realurl': request.POST.get('realurl')}, slug=slug, uuid=uuid_)) except paypal.PaypalError as error: paypal.paypal_log_cef(request, addon, uuid_, 'PayKey Failure', 'PAYKEYFAIL', 'There was an error getting the paykey') log.error('Error getting paykey, purchase of addon: %s' % addon.pk, exc_info=True) if paykey: contrib = Contribution(addon_id=addon.id, amount=amount, source=source, source_locale=request.LANG, uuid=str(uuid_), type=amo.CONTRIB_PENDING, paykey=paykey, user=request.amo_user) log.debug('Storing contrib for uuid: %s' % uuid_) # If this was a pre-approval, it's completed already, we'll # double check this with PayPal, just to be sure nothing went wrong. if status == 'COMPLETED': paypal.paypal_log_cef(request, addon, uuid_, 'Purchase', 'PURCHASE', 'A user purchased using pre-approval') log.debug('Status is completed for uuid: %s' % uuid_) if paypal.check_purchase(paykey) == 'COMPLETED': log.debug('Check purchase is completed for uuid: %s' % uuid_) contrib.type = amo.CONTRIB_PURCHASE else: # In this case PayPal disagreed, we should not be trusting # what get_paykey said. Which is a worry. log.error('Check purchase failed on uuid: %s' % uuid_) status = 'NOT-COMPLETED' contrib.save() else: log.error('No paykey present for uuid: %s' % uuid_) log.debug('Got paykey for addon: %s by user: %s' % (addon.pk, request.amo_user.pk)) url = '%s?paykey=%s' % (settings.PAYPAL_FLOW_URL, paykey) if request.POST.get('result_type') == 'json' or request.is_ajax(): return http.HttpResponse(json.dumps({'url': url, 'paykey': paykey, 'error': str(error), 'status': status}), content_type='application/json') # This is the non-Ajax fallback. if status != 'COMPLETED': return http.HttpResponseRedirect(url) messages.success(request, _('Purchase complete')) return http.HttpResponseRedirect(shared_url('addons.detail', addon))
def pay(request, signed_req, pay_req): paykey, status = '', '' preapproval = None if request.amo_user: preapproval = request.amo_user.get_preapproval() tier, price, currency = _get_price(pay_req, preapproval=preapproval) source = request.POST.get('source', '') product = pay_req['_config'].addon # L10n: {0} is the product name. {1} is the application name. contrib_for = (_(u'Firefox Marketplace in-app payment for {0} to {1}') .format(pay_req['request']['name'], product.name)) # TODO(solitude): solitude lib will create these for us. uuid_ = hashlib.md5(str(uuid.uuid4())).hexdigest() if waffle.flag_is_active(request, 'solitude-payments'): # TODO(solitude): when the migration of data is completed, we # will be able to remove this. Seller data is populated in solitude # on submission or devhub changes. If those don't occur, you won't be # able to sell at all. client.create_seller_for_pay(product) complete = reverse('inapp_pay.pay_status', args=[pay_req['_config'].pk, 'complete']) cancel = reverse('inapp_pay.pay_status', args=[pay_req['_config'].pk, 'cancel']) # TODO(bug 748137): remove retry is False. try: result = client.pay({'amount': price, 'currency': currency, 'buyer': request.amo_user, 'seller': product, 'memo': contrib_for, 'complete': complete, 'cancel': cancel}, retry=False) except client.Error as exc: paypal.paypal_log_cef(request, product, uuid_, 'in-app PayKey Failure', 'PAYKEYFAIL', 'There was an error getting the paykey') log.error(u'Error getting paykey, in-app payment: %s' % pay_req['_config'].pk, exc_info=True) InappPayLog.log(request, 'PAY_ERROR', config=pay_req['_config']) return render_error(request, exc) #TODO(solitude): just use the dictionary when solitude is live. paykey = result.get('pay_key', '') status = result.get('status', '') uuid_ = result.get('uuid', '') else: try: paykey, status = paypal.get_paykey(dict( amount=price, chains=settings.PAYPAL_CHAINS, currency=currency, email=product.paypal_id, ip=request.META.get('REMOTE_ADDR'), memo=contrib_for, pattern='inapp_pay.pay_status', preapproval=preapproval, qs={'realurl': request.POST.get('realurl')}, slug=pay_req['_config'].pk, # passed to pay_done() # via reverse() uuid=uuid_ )) except paypal.PaypalError, exc: paypal.paypal_log_cef(request, product, uuid_, 'in-app PayKey Failure', 'PAYKEYFAIL', 'There was an error getting the paykey') log.error(u'Error getting paykey, in-app payment: %s' % pay_req['_config'].pk, exc_info=True) InappPayLog.log(request, 'PAY_ERROR', config=pay_req['_config']) return render_error(request, exc)
def purchase_done(request, addon, status): result = '' if status == 'complete': uuid_ = request.GET.get('uuid') log.debug('Looking up contrib for uuid: %s' % uuid_) # The IPN may, or may not have come through. Which means looking for # a for pre or post IPN contributions. If both fail, then we've not # got a matching contribution. # # TODO(solitude): this will change when we figure out what to do # with Contributions. lookup = (Q(uuid=uuid_, type=amo.CONTRIB_PENDING) | Q(transaction_id=uuid_, type=amo.CONTRIB_PURCHASE)) con = get_object_or_404(Contribution, lookup) log.debug('Check purchase addon: %s, user: %s, paykey: %s' % (addon.pk, request.amo_user.pk, con.paykey[:10])) if waffle.flag_is_active(request, 'solitude-payments'): try: res = client.post_pay_check(data={'uuid': uuid_}) except client.Error: paypal.paypal_log_cef(request, addon, uuid_, 'Purchase Fail', 'PURCHASEFAIL', 'Checking purchase error') raise result = res['status'] # TODO(solitude): can be removed once solitude is live. else: try: result = paypal.check_purchase(con.paykey) if result == 'ERROR': paypal.paypal_log_cef(request, addon, uuid_, 'Purchase Fail', 'PURCHASEFAIL', 'Checking purchase error') raise except: paypal.paypal_log_cef(request, addon, uuid_, 'Purchase Fail', 'PURCHASEFAIL', 'There was an error checking purchase') log.error('Check purchase addon: %s, user: %s, paykey: %s' % (addon.pk, request.amo_user.pk, con.paykey[:10]), exc_info=True) result = 'ERROR' status = 'error' log.debug('Paypal returned: %s for paykey: %s' % (result, con.paykey[:10])) if result == 'COMPLETED' and con.type == amo.CONTRIB_PENDING: amo.log(amo.LOG.PURCHASE_ADDON, addon) con.update(type=amo.CONTRIB_PURCHASE) context = { 'realurl': request.GET.get('realurl', ''), 'status': status, 'result': result, 'product': addon, 'error': amo.PAYMENT_DETAILS_ERROR.get(result, '') } response = jingo.render(request, 'purchase/done.html', context) response['x-frame-options'] = 'allow' return response
amount=price, source=source, source_locale=request.LANG, currency=currency, uuid=str(uuid_), price_tier=tier, type=amo.CONTRIB_INAPP_PENDING, paykey=paykey, user=request.amo_user) log.debug('Storing in-app payment contrib for uuid: %s' % uuid_) # If this was a pre-approval, it's completed already, we'll # double check this with PayPal, just to be sure nothing went wrong. if status == 'COMPLETED': paypal.paypal_log_cef(request, product, uuid_, 'Purchase', 'PURCHASE', 'A user purchased using pre-approval') log.debug('Status is completed for uuid: %s' % uuid_) if waffle.flag_is_active(request, 'solitude-payments'): result = client.post_pay_check(data={'pay_key': paykey}) if result['status'] == 'COMPLETED': log.debug( 'Check in-app payment is completed for uuid: %s' % uuid_) contrib.type = amo.CONTRIB_INAPP else: # In this case PayPal disagreed, we should not be trusting # what get_paykey said. Which is a worry. log.error('Check in-app payment failed on uuid: %s' % uuid_)
def purchase(request, addon): log.debug("Starting purchase of addon: %s by user: %s" % (addon.pk, request.amo_user.pk)) amount = addon.premium.get_price() source = request.POST.get("source", "") uuid_ = hashlib.md5(str(uuid.uuid4())).hexdigest() # l10n: {0} is the addon name contrib_for = _(u"Purchase of {0}").format(jinja2.escape(addon.name)) # Default is USD. amount, currency = addon.premium.get_price(), "USD" # If tier is specified, then let's look it up. form = PriceCurrencyForm(data=request.POST, price=addon.premium.price) if form.is_valid(): tier = form.get_tier() if tier: amount, currency = tier.price, tier.currency paykey, status, error = "", "", "" preapproval = None if waffle.flag_is_active(request, "allow-pre-auth") and request.amo_user: preapproval = request.amo_user.get_preapproval() try: pattern = "addons.purchase.finished" slug = addon.slug if addon.is_webapp(): pattern = "apps.purchase.finished" slug = addon.app_slug paykey, status = paypal.get_paykey( dict( amount=amount, chains=settings.PAYPAL_CHAINS, currency=currency, email=addon.paypal_id, ip=request.META.get("REMOTE_ADDR"), memo=contrib_for, pattern=pattern, preapproval=preapproval, qs={"realurl": request.POST.get("realurl")}, slug=slug, uuid=uuid_, ) ) except paypal.PaypalError as error: paypal.paypal_log_cef( request, addon, uuid_, "PayKey Failure", "PAYKEYFAIL", "There was an error getting the paykey" ) log.error("Error getting paykey, purchase of addon: %s" % addon.pk, exc_info=True) if paykey: contrib = Contribution( addon_id=addon.id, amount=amount, source=source, source_locale=request.LANG, uuid=str(uuid_), type=amo.CONTRIB_PENDING, paykey=paykey, user=request.amo_user, ) log.debug("Storing contrib for uuid: %s" % uuid_) # If this was a pre-approval, it's completed already, we'll # double check this with PayPal, just to be sure nothing went wrong. if status == "COMPLETED": paypal.paypal_log_cef(request, addon, uuid_, "Purchase", "PURCHASE", "A user purchased using pre-approval") log.debug("Status is completed for uuid: %s" % uuid_) if paypal.check_purchase(paykey) == "COMPLETED": log.debug("Check purchase is completed for uuid: %s" % uuid_) contrib.type = amo.CONTRIB_PURCHASE else: # In this case PayPal disagreed, we should not be trusting # what get_paykey said. Which is a worry. log.error("Check purchase failed on uuid: %s" % uuid_) status = "NOT-COMPLETED" contrib.save() else: log.error("No paykey present for uuid: %s" % uuid_) log.debug("Got paykey for addon: %s by user: %s" % (addon.pk, request.amo_user.pk)) url = "%s?paykey=%s" % (settings.PAYPAL_FLOW_URL, paykey) if request.POST.get("result_type") == "json" or request.is_ajax(): return http.HttpResponse( json.dumps({"url": url, "paykey": paykey, "error": str(error), "status": status}), content_type="application/json", ) # This is the non-Ajax fallback. if status != "COMPLETED": return redirect(url) messages.success(request, _("Purchase complete")) return redirect(shared_url("addons.detail", addon))
def purchase(request, addon): log.debug('Starting purchase of addon: %s by user: %s' % (addon.pk, request.amo_user.pk)) amount = addon.premium.get_price() source = request.POST.get('source', '') uuid_ = hashlib.md5(str(uuid.uuid4())).hexdigest() # L10n: {0} is the addon name. contrib_for = (_(u'Mozilla Marketplace purchase of {0}').format( addon.name)) # Default is USD. amount, currency = addon.premium.get_price(), 'USD' # If tier is specified, then let's look it up. if waffle.switch_is_active('currencies'): form = PriceCurrencyForm(data=request.POST, addon=addon) if form.is_valid(): tier = form.get_tier() if tier: amount, currency = tier.price, tier.currency if not amount: # We won't write a contribution row for this because there # will not be a valid Paypal transaction. But we have to write the # Purchase row, something that writing to the contribution normally # does for us. AddonPurchase.objects.safer_get_or_create(addon=addon, user=request.amo_user) return http.HttpResponse(json.dumps({ 'url': '', 'paykey': '', 'error': '', 'status': 'COMPLETED' }), content_type='application/json') paykey, status, error = '', '', '' preapproval = None if request.amo_user: preapproval = request.amo_user.get_preapproval() # User the users default currency. if currency == 'USD' and preapproval and preapproval.currency: currency = preapproval.currency try: paykey, status = paypal.get_paykey( dict(amount=amount, chains=settings.PAYPAL_CHAINS, currency=currency, email=addon.paypal_id, ip=request.META.get('REMOTE_ADDR'), memo=contrib_for, pattern='purchase.done', preapproval=preapproval, qs={'realurl': request.POST.get('realurl')}, slug=addon.app_slug, uuid=uuid_)) except paypal.PaypalError as error: paypal.paypal_log_cef(request, addon, uuid_, 'PayKey Failure', 'PAYKEYFAIL', 'There was an error getting the paykey') log.error('Error getting paykey, purchase of addon: %s' % addon.pk, exc_info=True) if paykey: contrib = Contribution(addon_id=addon.id, amount=amount, source=source, source_locale=request.LANG, uuid=str(uuid_), type=amo.CONTRIB_PENDING, paykey=paykey, user=request.amo_user) log.debug('Storing contrib for uuid: %s' % uuid_) # If this was a pre-approval, it's completed already, we'll # double check this with PayPal, just to be sure nothing went wrong. if status == 'COMPLETED': paypal.paypal_log_cef(request, addon, uuid_, 'Purchase', 'PURCHASE', 'A user purchased using pre-approval') log.debug('Status is completed for uuid: %s' % uuid_) if paypal.check_purchase(paykey) == 'COMPLETED': log.debug('Check purchase is completed for uuid: %s' % uuid_) contrib.type = amo.CONTRIB_PURCHASE else: # In this case PayPal disagreed, we should not be trusting # what get_paykey said. Which is a worry. log.error('Check purchase failed on uuid: %s' % uuid_) status = 'NOT-COMPLETED' contrib.save() else: log.error('No paykey present for uuid: %s' % uuid_) log.debug('Got paykey for addon: %s by user: %s' % (addon.pk, request.amo_user.pk)) url = '%s?paykey=%s' % (settings.PAYPAL_FLOW_URL, paykey) if request.POST.get('result_type') == 'json' or request.is_ajax(): return http.HttpResponse(json.dumps({ 'url': url, 'paykey': paykey, 'error': str(error), 'status': status }), content_type='application/json') # This is the non-Ajax fallback. if status != 'COMPLETED': return redirect(url) messages.success(request, _('Purchase complete')) return redirect(addon.get_detail_url())
source=source, source_locale=request.LANG, currency=currency, uuid=str(uuid_), price_tier=tier, type=amo.CONTRIB_INAPP_PENDING, paykey=paykey, user=request.amo_user, ) log.debug("Storing in-app payment contrib for uuid: %s" % uuid_) # If this was a pre-approval, it's completed already, we'll # double check this with PayPal, just to be sure nothing went wrong. if status == "COMPLETED": paypal.paypal_log_cef( request, product, uuid_, "Purchase", "PURCHASE", "A user purchased using pre-approval" ) log.debug("Status is completed for uuid: %s" % uuid_) if waffle.flag_is_active(request, "solitude-payments"): result = client.post_pay_check(data={"pay_key": paykey}) if result["status"] == "COMPLETED": log.debug("Check in-app payment is completed for uuid: %s" % uuid_) contrib.type = amo.CONTRIB_INAPP else: # In this case PayPal disagreed, we should not be trusting # what get_paykey said. Which is a worry. log.error("Check in-app payment failed on uuid: %s" % uuid_) status = "NOT-COMPLETED" else: # TODO(solitude): remove this when solitude goes live.
def contribute(request, addon): contrib_type = request.POST.get("type", "suggested") is_suggested = contrib_type == "suggested" source = request.POST.get("source", "") comment = request.POST.get("comment", "") amount = {"suggested": addon.suggested_amount, "onetime": request.POST.get("onetime-amount", "")}.get( contrib_type, "" ) if not amount: amount = settings.DEFAULT_SUGGESTED_CONTRIBUTION form = ContributionForm({"amount": amount}) if not form.is_valid(): return http.HttpResponse( json.dumps({"error": "Invalid data.", "status": "", "url": "", "paykey": ""}), content_type="application/json", ) contribution_uuid = hashlib.md5(str(uuid.uuid4())).hexdigest() if addon.charity: # TODO(andym): Figure out how to get this in the addon authors # locale, rather than the contributors locale. name, paypal_id = (u"%s: %s" % (addon.name, addon.charity.name), addon.charity.paypal) else: name, paypal_id = addon.name, addon.paypal_id # l10n: {0} is the addon name contrib_for = _(u"Contribution for {0}").format(jinja2.escape(name)) paykey, error, status = "", "", "" try: paykey, status = paypal.get_paykey( dict( amount=amount, email=paypal_id, ip=request.META.get("REMOTE_ADDR"), memo=contrib_for, pattern="addons.paypal", slug=addon.slug, uuid=contribution_uuid, ) ) except paypal.PaypalError as error: paypal.paypal_log_cef( request, addon, contribution_uuid, "PayKey Failure", "PAYKEYFAIL", "There was an error getting the paykey" ) log.error("Error getting paykey, contribution for addon: %s" % addon.pk, exc_info=True) if paykey: contrib = Contribution( addon_id=addon.id, charity_id=addon.charity_id, amount=amount, source=source, source_locale=request.LANG, annoying=addon.annoying, uuid=str(contribution_uuid), is_suggested=is_suggested, suggested_amount=addon.suggested_amount, comment=comment, paykey=paykey, ) contrib.save() url = "%s?paykey=%s" % (settings.PAYPAL_FLOW_URL, paykey) if request.GET.get("result_type") == "json" or request.is_ajax(): # If there was an error getting the paykey, then JSON will # not have a paykey and the JS can cope appropriately. return http.HttpResponse( json.dumps({"url": url, "paykey": paykey, "error": str(error), "status": status}), content_type="application/json", ) return http.HttpResponseRedirect(url)
def pay(request, signed_req, pay_req): paykey, status = "", "" preapproval = None if request.amo_user: preapproval = request.amo_user.get_preapproval() tier, price, currency = _get_price(pay_req, preapproval=preapproval) source = request.POST.get("source", "") product = pay_req["_config"].addon # L10n: {0} is the product name. {1} is the application name. contrib_for = _(u"Firefox Marketplace in-app payment for {0} to {1}").format( pay_req["request"]["name"], product.name ) # TODO(solitude): solitude lib will create these for us. uuid_ = hashlib.md5(str(uuid.uuid4())).hexdigest() if waffle.flag_is_active(request, "solitude-payments"): # TODO(solitude): when the migration of data is completed, we # will be able to remove this. Seller data is populated in solitude # on submission or devhub changes. If those don't occur, you won't be # able to sell at all. client.create_seller_for_pay(product) complete = reverse("inapp_pay.pay_status", args=[pay_req["_config"].pk, "complete"]) cancel = reverse("inapp_pay.pay_status", args=[pay_req["_config"].pk, "cancel"]) # TODO(bug 748137): remove retry is False. try: result = client.pay( { "amount": price, "currency": currency, "buyer": request.amo_user, "seller": product, "memo": contrib_for, "complete": complete, "cancel": cancel, }, retry=False, ) except client.Error as exc: paypal.paypal_log_cef( request, product, uuid_, "in-app PayKey Failure", "PAYKEYFAIL", "There was an error getting the paykey" ) log.error(u"Error getting paykey, in-app payment: %s" % pay_req["_config"].pk, exc_info=True) InappPayLog.log(request, "PAY_ERROR", config=pay_req["_config"]) return render_error(request, exc) # TODO(solitude): just use the dictionary when solitude is live. paykey = result.get("pay_key", "") status = result.get("status", "") uuid_ = result.get("uuid", "") else: try: paykey, status = paypal.get_paykey( dict( amount=price, chains=settings.PAYPAL_CHAINS, currency=currency, email=product.paypal_id, ip=request.META.get("REMOTE_ADDR"), memo=contrib_for, pattern="inapp_pay.pay_status", preapproval=preapproval, qs={"realurl": request.POST.get("realurl")}, slug=pay_req["_config"].pk, # passed to pay_done() # via reverse() uuid=uuid_, ) ) except paypal.PaypalError, exc: paypal.paypal_log_cef( request, product, uuid_, "in-app PayKey Failure", "PAYKEYFAIL", "There was an error getting the paykey" ) log.error(u"Error getting paykey, in-app payment: %s" % pay_req["_config"].pk, exc_info=True) InappPayLog.log(request, "PAY_ERROR", config=pay_req["_config"]) return render_error(request, exc)
def contribute(request, addon): webapp = addon.is_webapp() contrib_type = request.GET.get('type', 'suggested') is_suggested = contrib_type == 'suggested' source = request.GET.get('source', '') comment = request.GET.get('comment', '') amount = { 'suggested': addon.suggested_amount, 'onetime': request.GET.get('onetime-amount', '') }.get(contrib_type, '') if not amount: amount = settings.DEFAULT_SUGGESTED_CONTRIBUTION contribution_uuid = hashlib.md5(str(uuid.uuid4())).hexdigest() if addon.charity: # TODO(andym): Figure out how to get this in the addon authors # locale, rather than the contributors locale. name, paypal_id = (u'%s: %s' % (addon.name, addon.charity.name), addon.charity.paypal) else: name, paypal_id = addon.name, addon.paypal_id # l10n: {0} is the addon name contrib_for = _(u'Contribution for {0}').format(jinja2.escape(name)) preapproval = None if waffle.flag_is_active(request, 'allow-pre-auth') and request.amo_user: preapproval = request.amo_user.get_preapproval() paykey, error, status = '', '', '' try: paykey, status = paypal.get_paykey( dict(amount=amount, email=paypal_id, ip=request.META.get('REMOTE_ADDR'), memo=contrib_for, pattern='%s.paypal' % ('apps' if webapp else 'addons'), preapproval=preapproval, slug=addon.slug, uuid=contribution_uuid)) except: paypal.paypal_log_cef(request, addon, contribution_uuid, 'PayKey Failure', 'PAYKEYFAIL', 'There was an error getting the paykey') log.error('Error getting paykey, contribution for addon: %s' % addon.pk, exc_info=True) error = _('There was an error communicating with PayPal.') if paykey: contrib = Contribution(addon_id=addon.id, charity_id=addon.charity_id, amount=amount, source=source, source_locale=request.LANG, annoying=addon.annoying, uuid=str(contribution_uuid), is_suggested=is_suggested, suggested_amount=addon.suggested_amount, comment=comment, paykey=paykey) contrib.save() url = '%s?paykey=%s' % (settings.PAYPAL_FLOW_URL, paykey) if request.GET.get('result_type') == 'json' or request.is_ajax(): # If there was an error getting the paykey, then JSON will # not have a paykey and the JS can cope appropriately. return http.HttpResponse(json.dumps({ 'url': url, 'paykey': paykey, 'error': error, 'status': status }), content_type='application/json') return http.HttpResponseRedirect(url)
def contribute(request, addon): commentlimit = 255 # Enforce paypal-imposed comment length limit contrib_type = request.POST.get('type', 'suggested') is_suggested = contrib_type == 'suggested' source = request.POST.get('source', '') comment = request.POST.get('comment', '') amount = { 'suggested': addon.suggested_amount, 'onetime': request.POST.get('onetime-amount', '') }.get(contrib_type, '') if not amount: amount = settings.DEFAULT_SUGGESTED_CONTRIBUTION form = ContributionForm({'amount': amount}) if len(comment) > commentlimit or not form.is_valid(): return http.HttpResponse(json.dumps({'error': 'Invalid data.', 'status': '', 'url': '', 'paykey': ''}), content_type='application/json') contribution_uuid = hashlib.md5(str(uuid.uuid4())).hexdigest() if addon.charity: # TODO(andym): Figure out how to get this in the addon authors # locale, rather than the contributors locale. name, paypal_id = (u'%s: %s' % (addon.name, addon.charity.name), addon.charity.paypal) else: name, paypal_id = addon.name, addon.paypal_id # l10n: {0} is the addon name contrib_for = _(u'Contribution for {0}').format(jinja2.escape(name)) paykey, error, status = '', '', '' try: paykey, status = paypal.get_paykey( dict(amount=amount, email=paypal_id, ip=request.META.get('REMOTE_ADDR'), memo=contrib_for, pattern='addons.paypal', slug=addon.slug, uuid=contribution_uuid)) except paypal.PaypalError as error: paypal.paypal_log_cef(request, addon, contribution_uuid, 'PayKey Failure', 'PAYKEYFAIL', 'There was an error getting the paykey') log.error('Error getting paykey, contribution for addon: %s' % addon.pk, exc_info=True) if paykey: contrib = Contribution(addon_id=addon.id, charity_id=addon.charity_id, amount=amount, source=source, source_locale=request.LANG, annoying=addon.annoying, uuid=str(contribution_uuid), is_suggested=is_suggested, suggested_amount=addon.suggested_amount, comment=comment, paykey=paykey) contrib.save() url = '%s?paykey=%s' % (settings.PAYPAL_FLOW_URL, paykey) if request.GET.get('result_type') == 'json' or request.is_ajax(): # If there was an error getting the paykey, then JSON will # not have a paykey and the JS can cope appropriately. return http.HttpResponse(json.dumps({'url': url, 'paykey': paykey, 'error': str(error), 'status': status}), content_type='application/json') return http.HttpResponseRedirect(url)
def purchase_done(request, addon, status): result = '' if status == 'complete': uuid_ = request.GET.get('uuid') log.debug('Looking up contrib for uuid: %s' % uuid_) # The IPN may, or may not have come through. Which means looking for # a for pre or post IPN contributions. If both fail, then we've not # got a matching contribution. # # TODO(solitude): this will change when we figure out what to do # with Contributions. lookup = (Q(uuid=uuid_, type=amo.CONTRIB_PENDING) | Q(transaction_id=uuid_, type=amo.CONTRIB_PURCHASE)) con = get_object_or_404(Contribution, lookup) log.debug('Check purchase addon: %s, user: %s, paykey: %s' % (addon.pk, request.amo_user.pk, con.paykey[:10])) if waffle.flag_is_active(request, 'solitude-payments'): try: res = client.post_pay_check(data={'uuid': uuid_}) except client.Error: paypal.paypal_log_cef(request, addon, uuid_, 'Purchase Fail', 'PURCHASEFAIL', 'Checking purchase error') raise result = res['status'] # TODO(solitude): can be removed once solitude is live. else: try: result = paypal.check_purchase(con.paykey) if result == 'ERROR': paypal.paypal_log_cef(request, addon, uuid_, 'Purchase Fail', 'PURCHASEFAIL', 'Checking purchase error') raise except: paypal.paypal_log_cef(request, addon, uuid_, 'Purchase Fail', 'PURCHASEFAIL', 'There was an error checking purchase') log.error('Check purchase addon: %s, user: %s, paykey: %s' % (addon.pk, request.amo_user.pk, con.paykey[:10]), exc_info=True) result = 'ERROR' status = 'error' log.debug('Paypal returned: %s for paykey: %s' % (result, con.paykey[:10])) if result == 'COMPLETED' and con.type == amo.CONTRIB_PENDING: amo.log(amo.LOG.PURCHASE_ADDON, addon) con.update(type=amo.CONTRIB_PURCHASE) context = {'realurl': request.GET.get('realurl', ''), 'status': status, 'result': result, 'product': addon, 'error': amo.PAYMENT_DETAILS_ERROR.get(result, '')} response = jingo.render(request, 'purchase/done.html', context) response['x-frame-options'] = 'allow' return response
def purchase(request, addon): log.debug('Starting purchase of addon: %s by user: %s' % (addon.pk, request.amo_user.pk)) amount = addon.premium.get_price() source = request.POST.get('source', '') uuid_ = hashlib.md5(str(uuid.uuid4())).hexdigest() # L10n: {0} is the addon name. contrib_for = (_(u'Mozilla Marketplace purchase of {0}') .format(addon.name)) # Default is USD. amount, currency = addon.premium.get_price(), 'USD' # If tier is specified, then let's look it up. if waffle.switch_is_active('currencies'): form = PriceCurrencyForm(data=request.POST, addon=addon) if form.is_valid(): tier = form.get_tier() if tier: amount, currency = tier.price, tier.currency if not amount: # We won't write a contribution row for this because there # will not be a valid Paypal transaction. But we have to write the # Purchase row, something that writing to the contribution normally # does for us. AddonPurchase.objects.safer_get_or_create(addon=addon, user=request.amo_user) return http.HttpResponse(json.dumps({'url': '', 'paykey': '', 'error': '', 'status': 'COMPLETED'}), content_type='application/json') paykey, status, error = '', '', '' preapproval = None if request.amo_user: preapproval = request.amo_user.get_preapproval() # User the users default currency. if currency == 'USD' and preapproval and preapproval.currency: currency = preapproval.currency try: paykey, status = paypal.get_paykey(dict( amount=amount, chains=settings.PAYPAL_CHAINS, currency=currency, email=addon.paypal_id, ip=request.META.get('REMOTE_ADDR'), memo=contrib_for, pattern='purchase.done', preapproval=preapproval, qs={'realurl': request.POST.get('realurl')}, slug=addon.app_slug, uuid=uuid_ )) except paypal.PaypalError as error: paypal.paypal_log_cef(request, addon, uuid_, 'PayKey Failure', 'PAYKEYFAIL', 'There was an error getting the paykey') log.error('Error getting paykey, purchase of addon: %s' % addon.pk, exc_info=True) if paykey: contrib = Contribution(addon_id=addon.id, amount=amount, source=source, source_locale=request.LANG, uuid=str(uuid_), type=amo.CONTRIB_PENDING, paykey=paykey, user=request.amo_user) log.debug('Storing contrib for uuid: %s' % uuid_) # If this was a pre-approval, it's completed already, we'll # double check this with PayPal, just to be sure nothing went wrong. if status == 'COMPLETED': paypal.paypal_log_cef(request, addon, uuid_, 'Purchase', 'PURCHASE', 'A user purchased using pre-approval') log.debug('Status is completed for uuid: %s' % uuid_) if paypal.check_purchase(paykey) == 'COMPLETED': log.debug('Check purchase is completed for uuid: %s' % uuid_) contrib.type = amo.CONTRIB_PURCHASE else: # In this case PayPal disagreed, we should not be trusting # what get_paykey said. Which is a worry. log.error('Check purchase failed on uuid: %s' % uuid_) status = 'NOT-COMPLETED' contrib.save() else: log.error('No paykey present for uuid: %s' % uuid_) log.debug('Got paykey for addon: %s by user: %s' % (addon.pk, request.amo_user.pk)) url = '%s?paykey=%s' % (settings.PAYPAL_FLOW_URL, paykey) if request.POST.get('result_type') == 'json' or request.is_ajax(): return http.HttpResponse(json.dumps({'url': url, 'paykey': paykey, 'error': str(error), 'status': status}), content_type='application/json') # This is the non-Ajax fallback. if status != 'COMPLETED': return redirect(url) messages.success(request, _('Purchase complete')) return redirect(addon.get_detail_url())
def contribute(request, addon): webapp = addon.is_webapp() contrib_type = request.POST.get('type', 'suggested') is_suggested = contrib_type == 'suggested' source = request.POST.get('source', '') comment = request.POST.get('comment', '') amount = { 'suggested': addon.suggested_amount, 'onetime': request.POST.get('onetime-amount', '') }.get(contrib_type, '') if not amount: amount = settings.DEFAULT_SUGGESTED_CONTRIBUTION # This is all going to get shoved into solitude. Temporary. form = ContributionForm({'amount': amount}) if not form.is_valid(): return http.HttpResponse(json.dumps({'error': 'Invalid data.', 'status': '', 'url': '', 'paykey': ''}), content_type='application/json') contribution_uuid = hashlib.md5(str(uuid.uuid4())).hexdigest() if addon.charity: # TODO(andym): Figure out how to get this in the addon authors # locale, rather than the contributors locale. name, paypal_id = (u'%s: %s' % (addon.name, addon.charity.name), addon.charity.paypal) else: name, paypal_id = addon.name, addon.paypal_id # l10n: {0} is the addon name contrib_for = _(u'Contribution for {0}').format(jinja2.escape(name)) preapproval = None if waffle.flag_is_active(request, 'allow-pre-auth') and request.amo_user: preapproval = request.amo_user.get_preapproval() paykey, error, status = '', '', '' try: paykey, status = paypal.get_paykey( dict(amount=amount, email=paypal_id, ip=request.META.get('REMOTE_ADDR'), memo=contrib_for, pattern='%s.paypal' % ('apps' if webapp else 'addons'), preapproval=preapproval, slug=addon.slug, uuid=contribution_uuid)) except paypal.PaypalError as error: paypal.paypal_log_cef(request, addon, contribution_uuid, 'PayKey Failure', 'PAYKEYFAIL', 'There was an error getting the paykey') log.error('Error getting paykey, contribution for addon: %s' % addon.pk, exc_info=True) if paykey: contrib = Contribution(addon_id=addon.id, charity_id=addon.charity_id, amount=amount, source=source, source_locale=request.LANG, annoying=addon.annoying, uuid=str(contribution_uuid), is_suggested=is_suggested, suggested_amount=addon.suggested_amount, comment=comment, paykey=paykey) contrib.save() url = '%s?paykey=%s' % (settings.PAYPAL_FLOW_URL, paykey) if request.GET.get('result_type') == 'json' or request.is_ajax(): # If there was an error getting the paykey, then JSON will # not have a paykey and the JS can cope appropriately. return http.HttpResponse(json.dumps({'url': url, 'paykey': paykey, 'error': str(error), 'status': status}), content_type='application/json') return http.HttpResponseRedirect(url)
def purchase(request, addon): log.debug('Starting purchase of addon: %s by user: %s' % (addon.pk, request.amo_user.pk)) amount = addon.premium.get_price() source = request.POST.get('source', '') uuid_ = hashlib.md5(str(uuid.uuid4())).hexdigest() # l10n: {0} is the addon name contrib_for = _(u'Purchase of {0}').format(jinja2.escape(addon.name)) # Default is USD. amount, currency = addon.premium.get_price(), 'USD' # If tier is specified, then let's look it up. form = PriceCurrencyForm(data=request.POST, addon=addon) if form.is_valid(): tier = form.get_tier() if tier: amount, currency = tier.price, tier.currency paykey, status, error = '', '', '' preapproval = None if waffle.flag_is_active(request, 'allow-pre-auth') and request.amo_user: preapproval = request.amo_user.get_preapproval() try: pattern = 'addons.purchase.finished' slug = addon.slug if addon.is_webapp(): pattern = 'apps.purchase.finished' slug = addon.app_slug paykey, status = paypal.get_paykey(dict( amount=amount, chains=settings.PAYPAL_CHAINS, currency=currency, email=addon.paypal_id, ip=request.META.get('REMOTE_ADDR'), memo=contrib_for, pattern=pattern, preapproval=preapproval, qs={'realurl': request.POST.get('realurl')}, slug=slug, uuid=uuid_)) except paypal.PaypalError as error: paypal.paypal_log_cef(request, addon, uuid_, 'PayKey Failure', 'PAYKEYFAIL', 'There was an error getting the paykey') log.error('Error getting paykey, purchase of addon: %s' % addon.pk, exc_info=True) if paykey: contrib = Contribution(addon_id=addon.id, amount=amount, source=source, source_locale=request.LANG, uuid=str(uuid_), type=amo.CONTRIB_PENDING, paykey=paykey, user=request.amo_user) log.debug('Storing contrib for uuid: %s' % uuid_) # If this was a pre-approval, it's completed already, we'll # double check this with PayPal, just to be sure nothing went wrong. if status == 'COMPLETED': paypal.paypal_log_cef(request, addon, uuid_, 'Purchase', 'PURCHASE', 'A user purchased using pre-approval') log.debug('Status is completed for uuid: %s' % uuid_) if paypal.check_purchase(paykey) == 'COMPLETED': log.debug('Check purchase is completed for uuid: %s' % uuid_) contrib.type = amo.CONTRIB_PURCHASE else: # In this case PayPal disagreed, we should not be trusting # what get_paykey said. Which is a worry. log.error('Check purchase failed on uuid: %s' % uuid_) status = 'NOT-COMPLETED' contrib.save() else: log.error('No paykey present for uuid: %s' % uuid_) log.debug('Got paykey for addon: %s by user: %s' % (addon.pk, request.amo_user.pk)) url = '%s?paykey=%s' % (settings.PAYPAL_FLOW_URL, paykey) if request.POST.get('result_type') == 'json' or request.is_ajax(): return http.HttpResponse(json.dumps({'url': url, 'paykey': paykey, 'error': str(error), 'status': status}), content_type='application/json') # This is the non-Ajax fallback. if status != 'COMPLETED': return http.HttpResponseRedirect(url) messages.success(request, _('Purchase complete')) return http.HttpResponseRedirect(shared_url('addons.detail', addon))
def purchase(request, addon): amount, currency, uuid_, contrib_for = start_purchase(request, addon) if not amount: # We won't write a contribution row for this because there # will not be a valid Paypal transaction. But we have to write the # Purchase row, something that writing to the contribution normally # does for us. AddonPurchase.objects.safer_get_or_create(addon=addon, user=request.amo_user) return http.HttpResponse(json.dumps({'url': '', 'paykey': '', 'error': '', 'status': 'COMPLETED'}), content_type='application/json') paykey, status, error = '', '', '' # TODO(solitude): remove this, pre-approval and currency will be # stored in solitude. preapproval = None if (not waffle.flag_is_active(request, 'solitude-payments') and request.amo_user): preapproval = request.amo_user.get_preapproval() # User the users default currency. if currency == 'USD' and preapproval and preapproval.currency: currency = preapproval.currency if waffle.flag_is_active(request, 'solitude-payments'): # Now call the client. result = {} try: result = client.pay({'amount': amount, 'currency': currency, 'buyer': request.amo_user, 'seller': addon, 'memo': contrib_for}) except client.Error as error: # Note that by assigning this to error, it will go into the return # value for the json. General solitude errors will then be # reported back to the user. paypal.paypal_log_cef(request, addon, uuid_, 'PayKey Failure', 'PAYKEYFAIL', 'There was an error getting the paykey') log.error('Error getting paykey: %s' % addon.pk, exc_info=True) # TODO(solitude): just use the dictionary when solitude is live. paykey = result.get('pay_key', '') status = result.get('status', '') uuid_ = result.get('uuid', '') else: # TODO(solitude): remove this when solitude goes live. try: paykey, status = paypal.get_paykey(dict( amount=amount, chains=settings.PAYPAL_CHAINS, currency=currency, email=addon.paypal_id, ip=request.META.get('REMOTE_ADDR'), memo=contrib_for, pattern='purchase.done', preapproval=preapproval, qs={'realurl': request.POST.get('realurl')}, slug=addon.app_slug, uuid=uuid_ )) except paypal.PaypalError as error: paypal.paypal_log_cef(request, addon, uuid_, 'PayKey Failure', 'PAYKEYFAIL', 'There was an error getting the paykey') log.error('Error getting paykey, purchase of addon: %s' % addon.pk, exc_info=True) if paykey: # TODO(solitude): at some point we'll have to see what to do with # contributions. download_source = request.REQUEST.get('src', '') contrib = Contribution(addon_id=addon.id, amount=amount, source=download_source, source_locale=request.LANG, uuid=str(uuid_), type=amo.CONTRIB_PENDING, paykey=paykey, user=request.amo_user, price_tier=addon.premium.price, client_data=ClientData.get_or_create(request)) log.debug('Storing contrib for uuid: %s' % uuid_) # If this was a pre-approval, it's completed already, we'll # double check this with PayPal, just to be sure nothing went wrong. if status == 'COMPLETED': paypal.paypal_log_cef(request, addon, uuid_, 'Purchase', 'PURCHASE', 'A user purchased using pre-approval') log.debug('Status completed for uuid: %s' % uuid_) if waffle.flag_is_active(request, 'solitude-payments'): result = client.post_pay_check(data={'pay_key': paykey}) if result['status'] == 'COMPLETED': contrib.type = amo.CONTRIB_PURCHASE else: log.error('Check purchase failed on uuid: %s' % uuid_) status = 'NOT-COMPLETED' else: #TODO(solitude): remove this when solitude goes live. if paypal.check_purchase(paykey) == 'COMPLETED': log.debug('Check purchase completed for uuid: %s' % uuid_) contrib.type = amo.CONTRIB_PURCHASE else: # In this case PayPal disagreed, we should not be trusting # what get_paykey said. Which is a worry. log.error('Check purchase failed on uuid: %s' % uuid_) status = 'NOT-COMPLETED' contrib.save() else: log.error('No paykey present for uuid: %s' % uuid_) log.debug('Got paykey for addon: %s by user: %s' % (addon.pk, request.amo_user.pk)) url = '%s?paykey=%s' % (settings.PAYPAL_FLOW_URL, paykey) if request.POST.get('result_type') == 'json' or request.is_ajax(): return http.HttpResponse(json.dumps({'url': url, 'paykey': paykey, 'error': str(error), 'status': status}), content_type='application/json') # This is the non-Ajax fallback. if status != 'COMPLETED': return http.HttpResponseRedirect(url) messages.success(request, _('Purchase complete')) return http.HttpResponseRedirect(addon.get_detail_url())
return render_error(request, exc) with transaction.commit_on_success(): contrib = Contribution(addon_id=product.id, amount=price, source=source, source_locale=request.LANG, currency=currency, uuid=str(uuid_), price_tier=tier, type=amo.CONTRIB_INAPP_PENDING, paykey=paykey, user=request.amo_user) log.debug('Storing in-app payment contrib for uuid: %s' % uuid_) # If this was a pre-approval, it's completed already, we'll # double check this with PayPal, just to be sure nothing went wrong. if status == 'COMPLETED': paypal.paypal_log_cef(request, product, uuid_, 'Purchase', 'PURCHASE', 'A user purchased using pre-approval') log.debug('Status is completed for uuid: %s' % uuid_) if waffle.flag_is_active(request, 'solitude-payments'): result = client.post_pay_check(data={'pay_key': paykey}) if result['status'] == 'COMPLETED': log.debug('Check in-app payment is completed for uuid: %s' % uuid_) contrib.type = amo.CONTRIB_INAPP else: # In this case PayPal disagreed, we should not be trusting # what get_paykey said. Which is a worry. log.error('Check in-app payment failed on uuid: %s' % uuid_) status = 'NOT-COMPLETED'
def pay(request, signed_req, pay_req): paykey, status = '', '' preapproval = None if request.amo_user: preapproval = request.amo_user.get_preapproval() tier, price, currency = _get_price(pay_req, preapproval=preapproval) source = request.POST.get('source', '') product = pay_req['_config'].addon # L10n: {0} is the product name. {1} is the application name. contrib_for = ( _(u'Firefox Marketplace in-app payment for {0} to {1}').format( pay_req['request']['name'], product.name)) # TODO(solitude): solitude lib will create these for us. uuid_ = hashlib.md5(str(uuid.uuid4())).hexdigest() if waffle.flag_is_active(request, 'solitude-payments'): # TODO(solitude): when the migration of data is completed, we # will be able to remove this. Seller data is populated in solitude # on submission or devhub changes. If those don't occur, you won't be # able to sell at all. client.create_seller_for_pay(product) complete = reverse('inapp_pay.pay_status', args=[pay_req['_config'].pk, 'complete']) cancel = reverse('inapp_pay.pay_status', args=[pay_req['_config'].pk, 'cancel']) # TODO(bug 748137): remove retry is False. try: result = client.pay( { 'amount': price, 'currency': currency, 'buyer': request.amo_user, 'seller': product, 'memo': contrib_for, 'complete': complete, 'cancel': cancel }, retry=False) except client.Error as exc: paypal.paypal_log_cef(request, product, uuid_, 'in-app PayKey Failure', 'PAYKEYFAIL', 'There was an error getting the paykey') log.error(u'Error getting paykey, in-app payment: %s' % pay_req['_config'].pk, exc_info=True) InappPayLog.log(request, 'PAY_ERROR', config=pay_req['_config']) return render_error(request, exc) #TODO(solitude): just use the dictionary when solitude is live. paykey = result.get('pay_key', '') status = result.get('status', '') uuid_ = result.get('uuid', '') else: try: paykey, status = paypal.get_paykey( dict( amount=price, chains=settings.PAYPAL_CHAINS, currency=currency, email=product.paypal_id, ip=request.META.get('REMOTE_ADDR'), memo=contrib_for, pattern='inapp_pay.pay_status', preapproval=preapproval, qs={'realurl': request.POST.get('realurl')}, slug=pay_req['_config'].pk, # passed to pay_done() # via reverse() uuid=uuid_)) except paypal.PaypalError, exc: paypal.paypal_log_cef(request, product, uuid_, 'in-app PayKey Failure', 'PAYKEYFAIL', 'There was an error getting the paykey') log.error(u'Error getting paykey, in-app payment: %s' % pay_req['_config'].pk, exc_info=True) InappPayLog.log(request, 'PAY_ERROR', config=pay_req['_config']) return render_error(request, exc)
def purchase(request, addon): amount, currency, uuid_, contrib_for = start_purchase(request, addon) if not amount: # We won't write a contribution row for this because there # will not be a valid Paypal transaction. But we have to write the # Purchase row, something that writing to the contribution normally # does for us. AddonPurchase.objects.safer_get_or_create(addon=addon, user=request.amo_user) return http.HttpResponse(json.dumps({ 'url': '', 'paykey': '', 'error': '', 'status': 'COMPLETED' }), content_type='application/json') paykey, status, error = '', '', '' # TODO(solitude): remove this, pre-approval and currency will be # stored in solitude. preapproval = None if (not waffle.flag_is_active(request, 'solitude-payments') and request.amo_user): preapproval = request.amo_user.get_preapproval() # User the users default currency. if currency == 'USD' and preapproval and preapproval.currency: currency = preapproval.currency if waffle.flag_is_active(request, 'solitude-payments'): # Now call the client. result = {} try: result = client.pay({ 'amount': amount, 'currency': currency, 'buyer': request.amo_user, 'seller': addon, 'memo': contrib_for }) except client.Error as error: # Note that by assigning this to error, it will go into the return # value for the json. General solitude errors will then be # reported back to the user. paypal.paypal_log_cef(request, addon, uuid_, 'PayKey Failure', 'PAYKEYFAIL', 'There was an error getting the paykey') log.error('Error getting paykey: %s' % addon.pk, exc_info=True) # TODO(solitude): just use the dictionary when solitude is live. paykey = result.get('pay_key', '') status = result.get('status', '') uuid_ = result.get('uuid', '') else: # TODO(solitude): remove this when solitude goes live. try: paykey, status = paypal.get_paykey( dict(amount=amount, chains=settings.PAYPAL_CHAINS, currency=currency, email=addon.paypal_id, ip=request.META.get('REMOTE_ADDR'), memo=contrib_for, pattern='purchase.done', preapproval=preapproval, qs={'realurl': request.POST.get('realurl')}, slug=addon.app_slug, uuid=uuid_)) except paypal.PaypalError as error: paypal.paypal_log_cef(request, addon, uuid_, 'PayKey Failure', 'PAYKEYFAIL', 'There was an error getting the paykey') log.error('Error getting paykey, purchase of addon: %s' % addon.pk, exc_info=True) if paykey: # TODO(solitude): at some point we'll have to see what to do with # contributions. download_source = request.REQUEST.get('src', '') contrib = Contribution(addon_id=addon.id, amount=amount, source=download_source, source_locale=request.LANG, uuid=str(uuid_), type=amo.CONTRIB_PENDING, paykey=paykey, user=request.amo_user, price_tier=addon.premium.price, client_data=ClientData.get_or_create(request)) log.debug('Storing contrib for uuid: %s' % uuid_) # If this was a pre-approval, it's completed already, we'll # double check this with PayPal, just to be sure nothing went wrong. if status == 'COMPLETED': paypal.paypal_log_cef(request, addon, uuid_, 'Purchase', 'PURCHASE', 'A user purchased using pre-approval') log.debug('Status completed for uuid: %s' % uuid_) if waffle.flag_is_active(request, 'solitude-payments'): result = client.post_pay_check(data={'pay_key': paykey}) if result['status'] == 'COMPLETED': contrib.type = amo.CONTRIB_PURCHASE else: log.error('Check purchase failed on uuid: %s' % uuid_) status = 'NOT-COMPLETED' else: #TODO(solitude): remove this when solitude goes live. if paypal.check_purchase(paykey) == 'COMPLETED': log.debug('Check purchase completed for uuid: %s' % uuid_) contrib.type = amo.CONTRIB_PURCHASE else: # In this case PayPal disagreed, we should not be trusting # what get_paykey said. Which is a worry. log.error('Check purchase failed on uuid: %s' % uuid_) status = 'NOT-COMPLETED' contrib.save() else: log.error('No paykey present for uuid: %s' % uuid_) log.debug('Got paykey for addon: %s by user: %s' % (addon.pk, request.amo_user.pk)) url = '%s?paykey=%s' % (settings.PAYPAL_FLOW_URL, paykey) if request.POST.get('result_type') == 'json' or request.is_ajax(): return http.HttpResponse(json.dumps({ 'url': url, 'paykey': paykey, 'error': str(error), 'status': status }), content_type='application/json') # This is the non-Ajax fallback. if status != 'COMPLETED': return http.HttpResponseRedirect(url) messages.success(request, _('Purchase complete')) return http.HttpResponseRedirect(addon.get_detail_url())