Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"""
sync_subscribers_from_paddle command.
"""
from django.core.management.base import BaseCommand

from ...models import Subscription


class Command(BaseCommand):
"""Sync subscribers from paddle."""

help = "Sync subscribers from paddle."

def handle(self, *args, **options):
"""Call sync_from_paddle_data for each subscriber returned by api_list."""
for sub_data in Subscription.api_list():
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • let's call it subscription_data

sub = Subscription.sync_from_paddle_data(sub_data)
print("Synchronized {0}".format(str(sub)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • since it can become a lot of subscriptions, this statement might be too verbose. Please make it a summary or add a parameter for verbose output
  • let's switch to self.stdout.write(self.style.SUCCESS('...')) - I'll change this in the plan-command as well

18 changes: 18 additions & 0 deletions djpaddle/migrations/0002_auto_20200410_0056.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 3.0.5 on 2020-04-10 00:56
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • please squash migrations


from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('djpaddle', '0001_initial'),
]

operations = [
migrations.AlterField(
model_name='subscription',
name='next_bill_date',
field=models.DateTimeField(blank=True, null=True),
),
]
23 changes: 23 additions & 0 deletions djpaddle/migrations/0003_auto_20200410_0455.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 3.0.5 on 2020-04-10 04:55

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('djpaddle', '0002_auto_20200410_0056'),
]

operations = [
migrations.AlterField(
model_name='subscription',
name='checkout_id',
field=models.TextField(),
),
migrations.AlterField(
model_name='subscription',
name='id',
field=models.TextField(primary_key=True, serialize=False),
),
]
68 changes: 60 additions & 8 deletions djpaddle/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from django.db.models.signals import post_save
from django.utils.translation import gettext_lazy as _
from django.dispatch import receiver
from django.utils import timezone

from . import settings, signals, api
from .fields import PaddleCurrencyCodeField
Expand Down Expand Up @@ -123,8 +124,9 @@ class Subscription(PaddleBaseModel):
(STATUS_PAUSED, _("paused")),
(STATUS_DELETED, _("deleted")),
)
PADDLE_URI_LIST = 'subscription/users'

id = models.CharField(max_length=32, primary_key=True)
id = models.TextField(primary_key=True)
subscriber = models.ForeignKey(
settings.DJPADDLE_SUBSCRIBER_MODEL,
related_name="subscriptions",
Expand All @@ -134,12 +136,13 @@ class Subscription(PaddleBaseModel):
)

cancel_url = models.URLField()
checkout_id = models.CharField(max_length=32)
checkout_id = models.TextField()
currency = models.CharField(max_length=3)
email = models.EmailField()
event_time = models.DateTimeField()
marketing_consent = models.BooleanField()
next_bill_date = models.DateTimeField()
next_bill_date = models.DateTimeField(
null=True, blank=True)
passthrough = models.TextField()
quantity = models.IntegerField()
source = models.URLField()
Expand All @@ -151,6 +154,51 @@ class Subscription(PaddleBaseModel):
class Meta:
ordering = ["created_at"]

@classmethod
def api_list(cls):
return api.retrieve(uri=cls.PADDLE_URI_LIST)

@classmethod
def sync_from_paddle_data(cls, data):
pk = data.get('subscription_id', None)
# First, find and drop current sub with this data.
cls.objects.filter(pk=pk).delete()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please initialize kwargs with all parameters needed to create/update the Subscription except pk, then use update_or_create instead of deleting the Subscription right away.

  • [ } use update_or_create

kwargs = {}

try:
kwargs['subscriber'] = settings.get_subscriber_model().objects.get(
email=data["user_email"]
)
except settings.get_subscriber_model().DoesNotExist:
pass

try:
kwargs['plan'] = Plan.objects.get(pk=data.get("plan_id"))
except Plan.DoesNotExist:
print("Skipping, plan not found.")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • please use log.warn or make it a comment
  • please return None

return

# Now, create object with this pk.
sub = Subscription.objects.create(
id=pk,
cancel_url=data.get("cancel_url"),
checkout_id="", # ???
currency=data.get('last_payment').get("currency"),
email=data.get("user_email"),
event_time=timezone.now(), # ???
marketing_consent=data.get('marketing_consent'),
# next_bill_date can be null if user won't pay again...
next_bill_date=data.get("next_payment", {}).get("date", None),
passthrough="", # ???
quantity=data.get("last_payment", {}).get("amount", 0),
source="", # ???
status=data.get("state"),
unit_price=0.00, # ???
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@matteing shall we rather make this a default in the model field?

update_url = data.get('update_url'),
**kwargs
)
return sub
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • please rename to instance or subscription


@classmethod
def _sanitize_webhook_payload(cls, payload):
data = {}
Expand All @@ -160,10 +208,14 @@ def _sanitize_webhook_payload(cls, payload):
# transform `user_id` to subscriber ref
data["subscriber"] = None
subscriber_id = payload.pop("user_id", None)
if subscriber_id not in ["", None]:
data["subscriber"], created = settings.get_subscriber_model().objects.get_or_create(
email=payload["email"]
)
try:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Subscriber = settings.get_subscriber_model()
if subscriber_id not in ["", None]:
        try:        
                data[ "subscriber"] = Subscriber.objects.get(email=payload["email"])
        except settings.get_subscriber_model().DoesNotExist:
               data[ "subscriber"] = None

if subscriber_id not in ["", None]:
data[
"subscriber"] = settings.get_subscriber_model().objects.get(
email=payload["email"]
)
except settings.get_subscriber_model().DoesNotExist:
pass

# transform `subscription_plan_id` to plan ref
data["plan"] = None
Expand Down Expand Up @@ -192,7 +244,7 @@ def from_subscription_created(cls, payload):
def update_by_payload(cls, payload):
data = cls._sanitize_webhook_payload(payload)
pk = data.pop("id")
return cls.objects.update_or_create(pk, defaults=data)
return cls.objects.update_or_create(pk=pk, defaults=data)

def __str__(self):
return "Subscription <{}:{}>".format(str(self.subscriber), str(self.id))
Expand Down
2 changes: 2 additions & 0 deletions djpaddle/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

from . import views

app_name = 'djpaddle'

urlpatterns = [
path("webhook/", views.paddle_webhook_view, name="webhook"),
]