paddle_utils.py 2.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. """
  2. Verify incoming webhook from Paddle
  3. Code inspired from https://developer.paddle.com/webhook-reference/verifying-webhooks
  4. """
  5. import base64
  6. import collections
  7. # PHPSerialize can be found at https://pypi.python.org/pypi/phpserialize
  8. import phpserialize
  9. import requests
  10. from Crypto.Hash import SHA1
  11. # Crypto can be found at https://pypi.org/project/pycryptodome/
  12. from Crypto.PublicKey import RSA
  13. from Crypto.Signature import PKCS1_v1_5
  14. from app.config import PADDLE_PUBLIC_KEY_PATH, PADDLE_VENDOR_ID, PADDLE_AUTH_CODE
  15. # Your Paddle public key.
  16. from app.log import LOG
  17. with open(PADDLE_PUBLIC_KEY_PATH) as f:
  18. public_key = f.read()
  19. # Convert key from PEM to DER - Strip the first and last lines and newlines, and decode
  20. public_key_encoded = public_key[26:-25].replace("\n", "")
  21. public_key_der = base64.b64decode(public_key_encoded)
  22. def verify_incoming_request(form_data: dict) -> bool:
  23. """verify the incoming form_data"""
  24. # copy form data
  25. input_data = form_data.copy()
  26. signature = input_data["p_signature"]
  27. # Remove the p_signature parameter
  28. del input_data["p_signature"]
  29. # Ensure all the data fields are strings
  30. for field in input_data:
  31. input_data[field] = str(input_data[field])
  32. # Sort the data
  33. sorted_data = collections.OrderedDict(sorted(input_data.items()))
  34. # and serialize the fields
  35. serialized_data = phpserialize.dumps(sorted_data)
  36. # verify the data
  37. key = RSA.importKey(public_key_der)
  38. digest = SHA1.new()
  39. digest.update(serialized_data)
  40. verifier = PKCS1_v1_5.new(key)
  41. signature = base64.b64decode(signature)
  42. if verifier.verify(digest, signature):
  43. return True
  44. return False
  45. def cancel_subscription(subscription_id: int) -> bool:
  46. r = requests.post(
  47. "https://vendors.paddle.com/api/2.0/subscription/users_cancel",
  48. data={
  49. "vendor_id": PADDLE_VENDOR_ID,
  50. "vendor_auth_code": PADDLE_AUTH_CODE,
  51. "subscription_id": subscription_id,
  52. },
  53. )
  54. res = r.json()
  55. if not res["success"]:
  56. LOG.error(
  57. f"cannot cancel subscription {subscription_id}, paddle response: {res}"
  58. )
  59. return res["success"]
  60. def change_plan(subscription_id: int, plan_id) -> bool:
  61. r = requests.post(
  62. "https://vendors.paddle.com/api/2.0/subscription/users/update",
  63. data={
  64. "vendor_id": PADDLE_VENDOR_ID,
  65. "vendor_auth_code": PADDLE_AUTH_CODE,
  66. "subscription_id": subscription_id,
  67. "plan_id": plan_id,
  68. },
  69. )
  70. res = r.json()
  71. if not res["success"]:
  72. LOG.error(
  73. f"cannot change subscription {subscription_id} to {plan_id}, paddle response: {res}"
  74. )
  75. return res["success"]