Permalink
Browse files

Merge branch 'release/refunds'

  • Loading branch information...
2 parents 6929963 + 57fdb38 commit 2d30f94f843f4663244f554efe80d3eb82607f7a @bhagany bhagany committed Apr 3, 2012
@@ -8,13 +8,14 @@
from hiicart.gateway.braintree.ipn import BraintreeIPN
from hiicart.gateway.braintree.settings import SETTINGS as default_settings
from hiicart.gateway.braintree.tasks import update_payment_status
+from hiicart.models import HiiCart
log = logging.getLogger('hiicart.gateway.braintree.gateway')
+
class BraintreeGateway(PaymentGatewayBase):
"""Payment Gateway for Braintree."""
-
def __init__(self, cart):
super(BraintreeGateway, self).__init__("braintree", cart, default_settings)
self._require_settings(["MERCHANT_ID", "MERCHANT_KEY",
@@ -24,18 +25,15 @@ def __init__(self, cart):
self.settings["MERCHANT_KEY"],
self.settings["MERCHANT_PRIVATE_KEY"])
-
def _is_valid(self):
"""Return True if gateway is valid."""
# TODO: Query Braintree to validate credentials
return True
-
@property
def is_recurring(self):
return len(self.cart.recurring_lineitems) > 0
-
@property
def environment(self):
"""Determine which Braintree environment to use."""
@@ -44,20 +42,17 @@ def environment(self):
else:
return braintree.Environment.Sandbox
-
def submit(self, collect_address=False, cart_settings_kwargs=None, submit=False):
"""
Simply returns the gateway type to let the frontend know how to proceed.
"""
return SubmitResult("direct")
-
@property
def form(self):
"""Returns an instance of PaymentForm."""
return make_form(self.is_recurring)()
-
def start_transaction(self, request):
"""
Submits transaction details to Braintree and returns form data.
@@ -90,7 +85,6 @@ def start_transaction(self, request):
redirect_url)
return tr_data
-
def confirm_payment(self, request, gateway_dict=None):
"""
Confirms payment result with Braintree.
@@ -105,7 +99,7 @@ def confirm_payment(self, request, gateway_dict=None):
result = braintree.TransparentRedirect.confirm(request.META['QUERY_STRING'])
except Exception, e:
errors = {'non_field_errors': 'Request to payment gateway failed.'}
- return result_class(transaction_id=None,
+ return result_class(transaction_id=None,
success=False, status=None, errors=errors,
gateway_result=None)
@@ -125,7 +119,7 @@ def confirm_payment(self, request, gateway_dict=None):
gateway_result = result
if created:
return result_class(transaction_id=transaction_id,
- success=True, status=status,
+ success=True, status=status,
gateway_result=gateway_result)
errors = {}
@@ -146,18 +140,16 @@ def confirm_payment(self, request, gateway_dict=None):
for error in result.errors.deep_errors:
errors[error.attribute] = error.message
- return result_class(transaction_id=transaction_id,
+ return result_class(transaction_id=transaction_id,
success=False, status=status, errors=errors,
gateway_result=result)
-
- def update_payment_status(self, transaction_id):
+ def update_payment_status(self, transaction_id, cart_class=HiiCart):
try:
- update_payment_status.apply_async(args=[self.cart.id, transaction_id], countdown=300)
+ update_payment_status.apply_async(args=[self.cart.id, transaction_id], kwargs={'cart_class': cart_class}, countdown=300)
except Exception, e:
log.error("Error updating payment status for transaction %s: %s" % (transaction_id, e))
-
def create_discount_args(self, discount_id, num_billing_cycles=1, quantity=1, existing_discounts=None):
if not existing_discounts:
args = {
@@ -186,14 +178,13 @@ def create_discount_args(self, discount_id, num_billing_cycles=1, quantity=1, ex
}
return args
-
def apply_discount(self, subscription_id, discount_id, num_billing_cycles=1, quantity=1):
"""
Apply a discount to an existing subscription.
"""
subscription = braintree.Subscription.find(subscription_id)
- existing_discounts = filter(lambda d: d.id==discount_id, subscription.discounts)
+ existing_discounts = filter(lambda d: d.id == discount_id, subscription.discounts)
args = self.create_discount_args(discount_id, num_bulling_cycles, quantity, existing_discounts)
result = braintree.Subscription.update(subscription_id, args)
errors = {}
@@ -207,7 +198,6 @@ def apply_discount(self, subscription_id, discount_id, num_billing_cycles=1, qua
success=result.is_success, status=status, errors=errors,
gateway_result=result)
-
def cancel_recurring(self):
"""
Cancel a cart's subscription.
@@ -220,14 +210,13 @@ def cancel_recurring(self):
success=result.is_success, status=result.subscription.status,
gateway_result=result)
-
def charge_recurring(self, grace_period=None):
"""
Charge a cart's recurring item, if necessary.
NOTE: Currently only one recurring item is supported per cart,
so charge the first one found.
We use braintree's subscriptions for recurring billing, so we don't manually
- charge recurring payments. Instead, we poll braintree to get new
+ charge recurring payments. Instead, we poll braintree to get new
payments/transactions.
"""
if not grace_period:
@@ -243,7 +232,6 @@ def charge_recurring(self, grace_period=None):
for t in transactions:
handler.accept_payment(t)
-
def start_update(self, request):
"""
Start the process of updating payment information for a subscription.
@@ -282,7 +270,7 @@ def confirm_update(self, request):
result = braintree.TransparentRedirect.confirm(request.META['QUERY_STRING'])
except Exception, e:
errors = {'non_field_errors': 'Request to payment gateway failed.'}
- return SubscriptionResult(transaction_id=None,
+ return SubscriptionResult(transaction_id=None,
success=False, status=None, errors=errors,
gateway_result=None)
@@ -313,6 +301,28 @@ def confirm_update(self, request):
for error in result.errors.deep_errors:
errors[error.attribute] = error.message
- return SubscriptionResult(transaction_id=None,
+ return SubscriptionResult(transaction_id=None,
success=False, status=status, errors=errors,
gateway_result=result)
+
+ def change_subscription_amount(self, subscription_id, new_price):
+ result = braintree.Subscription.update(subscription_id, {
+ 'price': new_price,
+ 'options': {
+ 'prorate_charged': True,
+ 'revert_subscription_on_proration_failure': False
+ }
+ })
+ return SubscriptionResult(transaction_id=None, success=result.is_success, status=None, errors={}, gateway_result=result)
+
+ def refund_payment(self, payment, reason=None):
+ result = self.refund(payment, payment.amount)
+ payment.state = 'REFUND'
+ payment.save()
+ return result
+
+ def refund(self, payment, amount, reason=None):
+ result = braintree.Transaction.refund(payment.transaction_id, amount)
+ if result.is_success:
+ self._create_payment(amount * -1, result.transaction.id, 'REFUND')
+ return TransactionResult(transaction_id=result.transaction.id, success=result.is_success, status=None, errors={}, gateway_result=result)
@@ -9,9 +9,9 @@
@task
-def update_payment_status(hiicart_id, transaction_id, tries=0):
+def update_payment_status(hiicart_id, transaction_id, tries=0, cart_class=HiiCart):
"""Check the payment status of a Braintree transaction."""
- hiicart = HiiCart.objects.get(pk=hiicart_id)
+ hiicart = cart_class.objects.get(pk=hiicart_id)
handler = BraintreeIPN(hiicart)
done = handler.update_order_status(transaction_id)
# Reschedule the failed payment to run in 4 hours
@@ -64,8 +64,8 @@ def cancel_items(self, payment, items=None, reason=None):
self.cart.update_state()
return CancelResult(None)
- def cancel_recurring(self, payment, items=None, reason=None):
- return self.cancel_items(payment, items, reason)
+ def cancel_recurring(self):
+ return self.cancel_items(self.cart.payments.filter(state='PAID').order_by('-created')[0])
def charge_recurring(self, grace_period=None):
"""HiiCart doesn't currently support manually charging subscriptions with Google Checkout"""
@@ -104,16 +104,16 @@ def refund_payment(self, payment, reason=None):
"""
Refund the full amount of this payment
"""
- self.refund(payment.transaction_id, payment.amount, reason)
+ self.refund(payment, payment.amount, reason)
payment.state = 'REFUND'
payment.save()
- def refund(self, transaction_id, amount, reason=None):
+ def refund(self, payment, amount, reason=None):
"""Refund a payment."""
self._update_with_cart_settings({'request': None})
template = loader.get_template("gateway/google/refund.xml")
- ctx = Context({"transaction_id": transaction_id,
+ ctx = Context({"transaction_id": payment.transaction_id,
"reason": reason,
"comment": None,
"currency": self.settings["CURRENCY"],
@@ -100,7 +100,14 @@ def _get_checkout_data(self):
# Urls for returning user after leaving Paypal
if self.settings.get('RETURN_URL'):
- params['returnurl'] = self.settings['RETURN_URL']
+ return_url = self.settings['RETURN_URL']
+ if '?' in return_url:
+ return_url += '&cart='
+ else:
+ return_url += '?cart='
+ return_url += self.cart._cart_uuid
+ params['returnurl'] = return_url
+
if self.settings.get('CANCEL_URL'):
params['cancelurl'] = self.settings['CANCEL_URL']
@@ -287,6 +294,11 @@ def get_details(self, token):
else:
# No confirm_url specified, so assume we go straight to finalizing the order
url = self.settings["FINALIZE_URL"]
+ if '?' in url:
+ url += '&cart='
+ else:
+ url += '?cart='
+ url += self.cart._cart_uuid
session_args = {
'hiicart_paypal_express_token': token,
@@ -355,20 +367,24 @@ def refund_payment(self, payment, reason=None):
"""
Refund the full amount of this payment
"""
- self.refund(payment.transaction_id, payment.amount, reason)
+ self.refund(payment, payment.amount, reason)
payment.state = 'REFUND'
payment.save()
- def refund(self, transaction_id, amount, reason=None):
+ def refund(self, payment, amount, reason=None):
"""Refund a payment."""
- params = {
- 'transactionid': transaction_id,
- 'refundtype': 'Full',
- }
+ params = {'transactionid': payment.transaction_id}
+ if payment.amount == amount:
+ params['refundtype'] = 'Full'
+ else:
+ params.update({
+ 'refundtype': 'Partial',
+ 'amt': str(amount.quantize(Decimal('.01')))
+ })
+
self._do_nvp('RefundTransaction', params)
return SubmitResult(None)
-
def charge_recurring(self, grace_period=None):
"""This Paypal API doesn't support manually charging subscriptions."""
pass

0 comments on commit 2d30f94

Please sign in to comment.