Giter Site home page Giter Site logo

paddle-python / dj-paddle Goto Github PK

View Code? Open in Web Editor NEW
80.0 80.0 34.0 126 KB

Django + Paddle made Easy!

Home Page: https://dj-paddle.readthedocs.io/en/latest/getting_started.html

License: MIT License

Python 92.83% Makefile 3.44% HTML 3.73%
django paddle payments

dj-paddle's Introduction

Paddle Client

A python (3.5+) wrapper around the Paddle.com API

If you are looking at integrating Paddle with Django check out dj-paddle

The full documentation is available at: https://paddle-client.readthedocs.io

Note: Several of the Paddle Endpoints are currently not working as expected. See Failing endpoints below.

Quick start

Installation

pip install paddle-client

Basic Usage

To use the Paddle API you will need a Paddle Vendor ID and API key which can be found on Paddle's authentication page

from paddle import PaddleClient


paddle = PaddleClient(vendor_id=12345, api_key='myapikey')
paddle.list_products()

If vendor_id and api_key are not passed through when initialising Paddle will fall back and try and use environmental variables called PADDLE_VENDOR_ID and PADDLE_API_KEY

export PADDLE_VENDOR_ID=12345
export PADDLE_API_KEY="myapikey"
from paddle import PaddleClient


paddle = PaddleClient()
paddle.list_products()

Paddle sandbox environment

The Paddle sandbox environment is a separate Paddle environment which can be used for development and testing. You are required to create a new account in this environment, different to your production account.

Once you have this account setup and configured you can user the sandbox account by passing sandbox=True when initialising the Paddle Client. This will send all API calls to the Paddle sandbox URLs instead of the production URLs

from paddle import PaddleClient


paddle = PaddleClient(vendor_id=12345, api_key='myapikey', sandbox=True)

It is also possible to turn the sandbox environment on using an environmental variable called PADDLE_SANDBOX:

export PADDLE_SANDBOX="true"
from paddle import PaddleClient


paddle = PaddleClient(vendor_id=12345, api_key='myapikey')

Documentation

The full documentation is available on Read the Docs: https://paddle-client.readthedocs.io

Contributing

All contributions are welcome and appreciated. Please see CONTRIBUTING.md for more details including details on how to run tests etc.

Paddle Endpoints

The below endpoints from the Paddle API Reference have been implemented

For full details see the API Reference in the docs. This includes details on parameters and return types for all the different methods as well as other helper methods around the Paddle.com API.

See Usage below for quick examples.

Checkout API

Product API

Subscription API

Alert API

Usage

See the API Reference in the docs for full usage with param are return details.

# Checkout API
paddle.get_order_details(checkout_id='aaaa-bbbb-cccc-1234')
paddle.get_user_history(email='[email protected]')
paddle.get_prices(product_ids=[1234])

# Product API
paddle.list_coupons(product_id=1234)
paddle.create_coupon(
    coupon_type='product',
    discount_type='percentage',
    discount_amount=50,
    allowed_uses=1,
    recurring=False,
    currency='USD',
    product_ids=[1234],
    coupon_code='50%OFF',
    description='50% off coupon over $10',
    expires='2030-01-01 10:00:00',
    minimum_threshold=10,
    group='paddle-python',
)
paddle.delete_coupon(coupon_code='mycoupon', product_id=1234)
paddle.update_coupon(
    coupon_code='mycoupon',
    new_coupon_code='40%OFF',
    new_group='paddle-python-test',
    product_ids=[1234],
    expires='2030-01-01 10:00:00',
    allowed_uses=1,
    currency='USD',
    minimum_threshold=10,
    discount_amount=40,
    recurring=True
)
paddle.list_products()
paddle.list_transactions(entity='subscription', entity_id=1234)
paddle.refund_product_payment(order_id=1234, amount=0.01, reason='reason')

# Subscription API
paddle.list_plans()
paddle.get_plan(plan=123)
paddle.create_plan(
    plan_name='plan_name',
    plan_trial_days=14,
    plan_length=1,
    plan_type='month',
    main_currency_code='USD',
    initial_price_usd=50,
    recurring_price_usd=50,
)
paddle.list_subscription_users()
paddle.cancel_subscription(subscription_id=1234)
paddle.update_subscription(subscription_id=1234, pause=True)
paddle.update_subscription(
    subscription_id=1234,
    quantity=10.00,
    currency='USD',
    recurring_price=10.00,
    bill_immediately=False,
    plan_id=123,
    prorate=True,
    keep_modifiers=True,
    passthrough='passthrough',
)
paddle.pause_subscription(subscription_id=1234)
paddle.resume_subscription(subscription_id=1234)
paddle.add_modifier(subscription_id=1234, modifier_amount=10.5)
paddle.delete_modifier(modifier_id=10)
paddle.list_modifiers()
paddle.list_subscription_payments()
paddle.reschedule_subscription_payment(payment_id=4567, date='2030-01-01')
paddle.create_one_off_charge(
    subscription_id=1234,
    amount=0.0,
    charge_name="Add X on top of subscription"
)

# Alert API
paddle.get_webhook_history()

dj-paddle's People

Contributors

adamchainz avatar ekmecic avatar fpurchess avatar kennell avatar matteing avatar pyepye avatar timhanlon avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

dj-paddle's Issues

mock Paddle API

In order to be able to extensively test the API integration, we need to decide on and implement a way of mocking the Paddle API.

checkout_id is not long enough

  • dj-paddle version: 0.1.1
  • Django version: 3.1
  • Python version: 3.7
  • Operating System: Linux

Description

Subscription checkout_id has max_length of 32. However Paddle can send checkout_id's that are longer than that. In my instance I got a 35 character id.

Codecov migration to marketplace app

Hi, Tom from Codecov here.

We noticed that you are using Codecov with fairly high frequency, and we’re so excited to see that! However, because you are not using our app, you may have experienced issues with uploading reports or viewing coverage information. This is due to rate-limiting issues from GitHub.

In order to prevent any future outages, we ask that you move over to our GitHub app integration.

The process is extremely simple and shouldn’t require more than a few clicks, and you should not expect any downtime. By moving to our app, you will no longer need an admin or separate account to manage the relationship with GitHub as the team bot.

Let me know if you have any questions, or if I can help at all with this process.

Configurable Stale Linking Fields

Description

Ability to set custom linking fields for stale records. For example passthrough on Paddle side and customer_id on the app model side. It defaults to email on both sides, like the current code.

Implementation here: chrisgrande@ebdfaac

I had trouble getting the tests to run locally so I didn't touch them at the moment.

Thoughts?

Uppercase letters in user's email address causes webhook to fail on subscription creation

  • dj-paddle version: 0.1.2
  • Django version: 4.2.3
  • Python version: 3.9.9
  • Operating System: Linux

Description

In production, we had an error when a user created a subscription and a webhook was sent:

"Subscriber could not be found for subscription <subscription_id> with payload".

We narrowed down the problem to a case sensitivity issue. Specifically if a user registered on our site with an uppercase letter and then tried to subscribe, the Paddle JS front end would lowercase the email address by default. When the subscription_created webhook was fired, the mapping would fail to find the associated user and they wouldn't be successfully marked as having a paid subscription. Unfortunately this affected our very first paying customer - talk about bad luck!

What I Did

  • Create a user like '[email protected]' with an uppercase letter in their email address.
  • Try to initiate a subscription using the Paddle UI
  • Note that the UI will default to lowercasing their email address (you can also probably just manually change the casing in the Paddle modal).
  • Complete the payment
  • You get an error when the subscription_created webhook is fired stating that the user is not found.

Workaround

You can override the default mapping by setting the settings param DJPADDLE_SUBSCRIBER_BY_PAYLOAD. Then you can replace the default djpaddle.mappers.subscriber_by_payload() and make sure you're doing a case-insensitive search.

Fix

I believe the user search should be case-insensitive, since subscriptions_by_subscriber() is also doing a case-insensitive search. I'll try to submit a pull request.

cancellation_effective_date missing in subscription model

Paddle provides a cancellation_effective_date field which is currently missing in the subscription model:
https://developer.paddle.com/webhook-reference/3c0355dc446b0-subscription-cancelled
"The date the cancellation should come into effect, taking the customer’s most recent payment into account. The customer should be able to use the service they've subscribed to up until this date."

This is an important field because it is otherwise unknown how long the service should still be provided after cancellation.

Required line to be added in models.py under 'class Subscription(PaddleBaseModel):':
cancellation_effective_date = models.DateTimeField(null=True, blank=True)

Latest version needs to be released on PyPi

  • dj-paddle version:
  • Django version:
  • Python version:
  • Operating System:

Description

The latest version with the Checkout ID extended to 64 Chars is not on PyPy - it's still installing the old version.

What I Did

pip install dj-paddle

Add post_save signals to Subscriber models

A great idea would be to add post_save signals to subscriber models, so for example instead of working with raw request payloads in signals we can instead just use an instance of Subscriber passed to the signal.

Subscriptions have no cancel url

  • dj-paddle version: 0.1.2
  • Django version:.4.0.2
  • Python version: 3.9.4

Description

We were quite suprised that out of 7 subscription only 2 have a cancel_url attached to it (the same 2 are the only ones with an update_url). Our cancel UX depends on this to be defined so I was wondering when the cancel_url should be created and how I could force a resync.

Subscription Created webhook silently fails

  • dj-paddle version: 0.1.0.dev3
  • Django version: 2.1.15
  • Python version: 3.7
  • Operating System: OS X

Description

Webhook for Subscription Created silently fails.

What I Did

Using Paddle's Webhook Alert Testing to send a Subscription Created webhook returns a 200 OK, but fails to create objects for djpaddle.subscription or the subscriber model.

I have a branch ready here which successfully creates the objects.

Race condition when listening on both `subscription_payment_succeeded` and `subscription_created` for new subscriptions

  • dj-paddle version:
  • 0.1.2

Description

After upgrading to 0.1.2 I started to see errors with every new subscription:

IntegrityError at /paddle/webhook/
(1062, "Duplicate entry 'xxxxxxx' for key 'PRIMARY'")

It turns out the problem is that now both subscription_payment_succeeded and subscription_created try to create a new subscription entry in the database in parallel. subscription_payment_succeeded was added to the subscription_event with 0.1.2.

The code documentation for Subscription does not mention subscription_payment_succeeded as possible trigger.

Fortunately, the problem is not severe, as the first webhook invocation (subscription_created) will succeed at creating the subscription. The second call will fail with no consequence, the retry from Paddle will then succeed when the Subscription instance can already be found in the database.

A get_or_create should solve the problem here:

        try:
            subscription = cls.objects.get(pk=pk)
        except cls.DoesNotExist:
            return cls.objects.create(pk=pk, **data)

djpaddle_sync_plans_from_paddle errors out with Paddle error 107 - You don't have permission to access this resource

dj-paddle version: 0.1.2

user@b52850d97f85:/app$ ./manage.py djpaddle_sync_plans_from_paddle
Traceback (most recent call last):
  File "/app/./manage.py", line 22, in <module>
    main()
  File "/app/./manage.py", line 18, in main
    execute_from_command_line(sys.argv)
  File "/usr/local/lib/python3.10/site-packages/django/core/management/__init__.py", line 446, in execute_from_command_line
    utility.execute()
  File "/usr/local/lib/python3.10/site-packages/django/core/management/__init__.py", line 440, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/usr/local/lib/python3.10/site-packages/django/core/management/base.py", line 402, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/usr/local/lib/python3.10/site-packages/django/core/management/base.py", line 448, in execute
    output = self.handle(*args, **options)
  File "/usr/local/lib/python3.10/site-packages/djpaddle/management/commands/djpaddle_sync_plans_from_paddle.py", line 16, in handle
    for plan_data in Plan.api_list():
  File "/usr/local/lib/python3.10/site-packages/djpaddle/models.py", line 55, in api_list
    return paddle_client.list_plans()
  File "/usr/local/lib/python3.10/site-packages/paddle/_plans.py", line 19, in list_plans
    return self.post(url=url)
  File "/usr/local/lib/python3.10/site-packages/paddle/paddle.py", line 182, in post
    return self.request(**kwargs)
  File "/usr/local/lib/python3.10/site-packages/paddle/paddle.py", line 154, in request
    raise PaddleException(response_json['error'])
paddle.paddle.PaddleException: Paddle error 107 - You don't have permission to access this resource

I'm trying to test paddle integration with the sandbox environment. I have set the DJPADDLE_VENDOR_ID, DJ_PADDLE_API_KEY (the default one already created in the sandbox), DJPADDLE_PUBLIC_KEY with values from the sandbox-vendors.paddle.com. I've also set DJPADDLE_SANDBOX=True.

Sandbox webhook/sync failures

  • dj-paddle version: 0.1.2
  • Django version: 3.1.5
  • Python version: 3.8
  • Operating System: Manjaro Linux

Description

I am trying to use the sandbox for development.

  • I have configured my Sandbox with Plans
  • I have set DJPADDLE_SANDBOX to True
  • I have set both DJPADDLE_API_KEY and DJPADDLE_VENDOR_ID to the Sandbox values

What goes wrong:

  1. djpaddle_sync_plans_from_paddle fails with paddle.paddle.PaddleException: Paddle error 107 - You don't have permission to access this resource
  2. Webhooks return with a webhook validation failed (both the Webhook simulator, and when using the checkout)

What I expect:

  • Plans can sync
  • Webhook verification passes

What I Did

Notes:

  • The sync and webhooks work when using the production (non-Sandbox) paddle account.
  • I am using a React frontend but am able to successfully render both Sandbox and Production checkout's when I pass in the Vendor and Product id's for either.

For clarity what my script tags look like when using React:

// when using Prod, sandbox must be removed/commented out
    <script src="https://cdn.paddle.com/paddle/paddle.js"></script>
    <script type="text/javascript">
      Paddle.Setup({ vendor: xxxxxx, debug: true });
      Paddle.Environment.set('sandbox');
    </script>

Also, I copied and pasted in the is_valid_webhook and created my own webhook endpoint to figure out what's going wrong. The is_valid_webhook is returning a False when I use the sandbox but is True in production.

The verifier.verify(digest, signature) is failing, and returns a False.

Last point

I have tested the sandbox against paddle-client and it works. I can list plans, users, etc.


Tracebacks and responses below

Webhook Response

Bad Request: /paddle/webhook/
INFO:     172.23.0.1:50524 - "POST /paddle/webhook/ HTTP/1.1" 400 Bad Request

Sync traceback

root@5a4ce255489d:/app# python manage.py djpaddle_sync_plans_from_paddle
Traceback (most recent call last):
  File "manage.py", line 31, in <module>
    execute_from_command_line(sys.argv)
  File "/usr/local/lib/python3.8/site-packages/django/core/management/__init__.py", line 401, in execute_from_command_line
    utility.execute()
  File "/usr/local/lib/python3.8/site-packages/django/core/management/__init__.py", line 395, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/usr/local/lib/python3.8/site-packages/django/core/management/base.py", line 330, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/usr/local/lib/python3.8/site-packages/django/core/management/base.py", line 371, in execute
    output = self.handle(*args, **options)
  File "/usr/local/lib/python3.8/site-packages/djpaddle/management/commands/djpaddle_sync_plans_from_paddle.py", line 16, in handle
    for plan_data in Plan.api_list():
  File "/usr/local/lib/python3.8/site-packages/djpaddle/models.py", line 55, in api_list
    return paddle_client.list_plans()
  File "/usr/local/lib/python3.8/site-packages/paddle/_plans.py", line 19, in list_plans
    return self.post(url=url)
  File "/usr/local/lib/python3.8/site-packages/paddle/paddle.py", line 182, in post
    return self.request(**kwargs)
  File "/usr/local/lib/python3.8/site-packages/paddle/paddle.py", line 154, in request
    raise PaddleException(response_json['error'])
paddle.paddle.PaddleException: Paddle error 107 - You don't have permission to access this resource

Fin

I hope that is enough information to be helpful, and also, not a waste of your time.

Thanks for providing a great open-source package!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.