Ctrl K
ComunidadGuías…

app/paddle_utils.py

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
"""
Verify incoming webhook from Paddle
Code inspired from https://developer.paddle.com/webhook-reference/verifying-webhooks
"""

import base64
import collections

# PHPSerialize can be found at https://pypi.python.org/pypi/phpserialize
import phpserialize
import requests
from Crypto.Hash import SHA1

# Crypto can be found at https://pypi.org/project/pycryptodome/
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5

from app.config import PADDLE_PUBLIC_KEY_PATH, PADDLE_VENDOR_ID, PADDLE_AUTH_CODE

# Your Paddle public key.
from app.log import LOG
from app.models import User

with open(PADDLE_PUBLIC_KEY_PATH) as f:
    public_key = f.read()


# Convert key from PEM to DER - Strip the first and last lines and newlines, and decode
public_key_encoded = public_key[26:-25].replace("\n", "")
public_key_der = base64.b64decode(public_key_encoded)


def verify_incoming_request(form_data: dict) -> bool:
    """verify the incoming form_data"""
    # copy form data
    input_data = form_data.copy()

    signature = input_data["p_signature"]

    # Remove the p_signature parameter
    del input_data["p_signature"]

    # Ensure all the data fields are strings
    for field in input_data:
        input_data[field] = str(input_data[field])

    # Sort the data
    sorted_data = collections.OrderedDict(sorted(input_data.items()))

    # and serialize the fields
    serialized_data = phpserialize.dumps(sorted_data)

    # verify the data
    key = RSA.importKey(public_key_der)
    digest = SHA1.new()
    digest.update(serialized_data)
    verifier = PKCS1_v1_5.new(key)
    signature = base64.b64decode(signature)
    if verifier.verify(digest, signature):
        return True
    return False


def cancel_subscription(subscription_id: str) -> bool:
    r = requests.post(
        "https://vendors.paddle.com/api/2.0/subscription/users_cancel",
        data={
            "vendor_id": PADDLE_VENDOR_ID,
            "vendor_auth_code": PADDLE_AUTH_CODE,
            "subscription_id": subscription_id,
        },
    )
    res = r.json()
    if not res["success"]:
        LOG.e(f"cannot cancel subscription {subscription_id}, paddle response: {res}")

    return res["success"]


def change_plan(user: User, subscription_id: str, plan_id) -> (bool, str):
    """return whether the operation is successful and an optional error message"""
    r = requests.post(
        "https://vendors.paddle.com/api/2.0/subscription/users/update",
        data={
            "vendor_id": PADDLE_VENDOR_ID,
            "vendor_auth_code": PADDLE_AUTH_CODE,
            "subscription_id": subscription_id,
            "plan_id": plan_id,
        },
    )
    res = r.json()
    if not res["success"]:
        LOG.d(f"Could not change plan for user {user} in paddle. Response is {res}")
        try:
            # "unable to complete the resubscription because we could not charge the customer for the resubscription"
            if res["error"]["code"] == 147:
                LOG.w(
                    "could not charge the customer for the resubscription error %s,%s",
                    subscription_id,
                    user,
                )
                return False, "Your card cannot be charged"
        except KeyError:
            LOG.e(
                f"cannot change subscription {subscription_id} to {plan_id}, paddle response: {res}"
            )
            return False, ""

        return False, ""

    return res["success"], ""