Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement subscriber sync from Paddle API #19

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
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"),
]