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.

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