You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

210 lines
6.7 KiB

9 years ago
9 years ago
  1. from django.conf import settings
  2. from django.contrib.auth.models import User
  3. from django.db import models
  4. from django.template.loader import render_to_string
  5. if settings.USE_PAYMENTS:
  6. import braintree
  7. else:
  8. # hc.payments tests mock this object, so tests should
  9. # still be able to run:
  10. braintree = None
  11. ADDRESS_KEYS = ("first_name", "last_name", "company", "street_address",
  12. "extended_address", "locality", "region", "postal_code",
  13. "country_code_alpha2")
  14. class SubscriptionManager(models.Manager):
  15. def for_user(self, user):
  16. sub, created = Subscription.objects.get_or_create(user_id=user.id)
  17. return sub
  18. def by_transaction(self, transaction_id):
  19. try:
  20. tx = braintree.Transaction.find(transaction_id)
  21. except braintree.exceptions.NotFoundError:
  22. return None, None
  23. try:
  24. sub = self.get(customer_id=tx.customer_details.id)
  25. except Subscription.DoesNotExist:
  26. return None, None
  27. return sub, tx
  28. def by_braintree_webhook(self, request):
  29. sig = str(request.POST["bt_signature"])
  30. payload = str(request.POST["bt_payload"])
  31. doc = braintree.WebhookNotification.parse(sig, payload)
  32. assert doc.kind == "subscription_charged_successfully"
  33. sub = self.get(subscription_id=doc.subscription.id)
  34. return sub, doc.subscription.transactions[0]
  35. class Subscription(models.Model):
  36. user = models.OneToOneField(User, models.CASCADE, blank=True, null=True)
  37. customer_id = models.CharField(max_length=36, blank=True)
  38. payment_method_token = models.CharField(max_length=35, blank=True)
  39. subscription_id = models.CharField(max_length=10, blank=True)
  40. plan_id = models.CharField(max_length=10, blank=True)
  41. plan_name = models.CharField(max_length=50, blank=True)
  42. address_id = models.CharField(max_length=2, blank=True)
  43. send_invoices = models.BooleanField(default=True)
  44. invoice_email = models.EmailField(blank=True)
  45. objects = SubscriptionManager()
  46. @property
  47. def payment_method(self):
  48. if not self.payment_method_token:
  49. return None
  50. if not hasattr(self, "_pm"):
  51. self._pm = braintree.PaymentMethod.find(self.payment_method_token)
  52. return self._pm
  53. def _get_braintree_subscription(self):
  54. if not hasattr(self, "_sub"):
  55. self._sub = braintree.Subscription.find(self.subscription_id)
  56. return self._sub
  57. def get_client_token(self):
  58. return braintree.ClientToken.generate({
  59. "customer_id": self.customer_id
  60. })
  61. def update_payment_method(self, nonce):
  62. # Create customer record if it does not exist:
  63. if not self.customer_id:
  64. result = braintree.Customer.create({
  65. "email": self.user.email
  66. })
  67. if not result.is_success:
  68. return result
  69. self.customer_id = result.customer.id
  70. self.save()
  71. # Create payment method
  72. result = braintree.PaymentMethod.create({
  73. "customer_id": self.customer_id,
  74. "payment_method_nonce": nonce,
  75. "options": {"make_default": True}
  76. })
  77. if not result.is_success:
  78. return result
  79. self.payment_method_token = result.payment_method.token
  80. self.save()
  81. # Update an existing subscription to use this payment method
  82. if self.subscription_id:
  83. result = braintree.Subscription.update(self.subscription_id, {
  84. "payment_method_token": self.payment_method_token
  85. })
  86. if not result.is_success:
  87. return result
  88. def update_address(self, post_data):
  89. # Create customer record if it does not exist:
  90. if not self.customer_id:
  91. result = braintree.Customer.create({
  92. "email": self.user.email
  93. })
  94. if not result.is_success:
  95. return result
  96. self.customer_id = result.customer.id
  97. self.save()
  98. payload = {key: str(post_data.get(key)) for key in ADDRESS_KEYS}
  99. if self.address_id:
  100. result = braintree.Address.update(self.customer_id,
  101. self.address_id,
  102. payload)
  103. else:
  104. payload["customer_id"] = self.customer_id
  105. result = braintree.Address.create(payload)
  106. if result.is_success:
  107. self.address_id = result.address.id
  108. self.save()
  109. if not result.is_success:
  110. return result
  111. def setup(self, plan_id):
  112. result = braintree.Subscription.create({
  113. "payment_method_token": self.payment_method_token,
  114. "plan_id": plan_id
  115. })
  116. if result.is_success:
  117. self.subscription_id = result.subscription.id
  118. self.plan_id = plan_id
  119. if plan_id == "P20":
  120. self.plan_name = "Standard ($20 / month)"
  121. elif plan_id == "Y192":
  122. self.plan_name = "Standard ($192 / year)"
  123. elif plan_id == "P80":
  124. self.plan_name = "Plus ($80 / month)"
  125. elif plan_id == "Y768":
  126. self.plan_name = "Plus ($768 / year)"
  127. self.save()
  128. return result
  129. def cancel(self):
  130. if self.subscription_id:
  131. braintree.Subscription.cancel(self.subscription_id)
  132. self.subscription_id = ""
  133. self.plan_id = ""
  134. self.save()
  135. def pm_is_card(self):
  136. pm = self.payment_method
  137. return isinstance(pm, braintree.credit_card.CreditCard)
  138. def pm_is_paypal(self):
  139. pm = self.payment_method
  140. return isinstance(pm, braintree.paypal_account.PayPalAccount)
  141. def next_billing_date(self):
  142. o = self._get_braintree_subscription()
  143. return o.next_billing_date
  144. @property
  145. def address(self):
  146. if not hasattr(self, "_address"):
  147. try:
  148. self._address = braintree.Address.find(self.customer_id,
  149. self.address_id)
  150. except braintree.exceptions.NotFoundError:
  151. self._address = None
  152. return self._address
  153. def flattened_address(self):
  154. if self.address_id:
  155. ctx = {"a": self.address}
  156. return render_to_string("payments/address_plain.html", ctx)
  157. else:
  158. return self.user.email
  159. @property
  160. def transactions(self):
  161. if not hasattr(self, "_tx"):
  162. if not self.customer_id:
  163. self._tx = []
  164. else:
  165. self._tx = list(braintree.Transaction.search(braintree.TransactionSearch.customer_id == self.customer_id))
  166. return self._tx