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.

206 lines
6.2 KiB

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