Skip to content

Commit

Permalink
added getting requested payments info and cancel/remind a payment
Browse files Browse the repository at this point in the history
  • Loading branch information
mmohades committed Sep 24, 2020
1 parent 9fdf5fd commit e0e78de
Show file tree
Hide file tree
Showing 10 changed files with 280 additions and 20 deletions.
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ media


### Other ###
tests.py
venmo_api/test.py
test*.py

### Linux ###
*~
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def requirements():

setup(
name='venmo-api',
version='0.1.7',
version='0.2.0',
author="Mark Mohades",
license="GNU General Public License v3",
url='https://github.com/mmohades/venmo',
Expand Down
10 changes: 6 additions & 4 deletions venmo_api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from .models.json_schema import JSONSchema
from .models.user import User
from .models.transaction import Transaction
from .models.payment import Payment, PaymentStatus
from .models.payment_method import (PaymentMethod, PaymentRole, PaymentPrivacy)
from .utils.api_util import (deserialize, wrap_callback, warn, get_user_id, confirm, validate_access_token)
from .utils.api_client import ApiClient
Expand All @@ -13,9 +14,10 @@

__all__ = ["AuthenticationFailedError", "InvalidArgumentError", "InvalidHttpMethodError", "ArgumentMissingError",
"JSONDecodeError", "ResourceNotFoundError", "HttpCodeError", "NoPaymentMethodFoundError",
"string_to_timestamp", "get_phone_model_from_json", "random_device_id", "deserialize", "wrap_callback",
"warn", "confirm", "get_user_id", "validate_access_token",
"JSONSchema", "User", "Transaction", "PaymentMethod", "PaymentRole", "PaymentPrivacy",
"ApiClient", "AuthenticationApi", "UserApi", "PaymentApi",
"NoPendingPaymentToUpdateError", "AlreadyRemindedPaymentError", "string_to_timestamp",
"get_phone_model_from_json", "random_device_id",
"deserialize", "wrap_callback", "warn", "confirm", "get_user_id", "validate_access_token",
"JSONSchema", "User", "Transaction", "Payment", "PaymentStatus", "PaymentMethod", "PaymentRole",
"PaymentPrivacy", "ApiClient", "AuthenticationApi", "UserApi", "PaymentApi",
"Client"
]
108 changes: 106 additions & 2 deletions venmo_api/apis/payment_api.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from venmo_api import ApiClient
from venmo_api import ApiClient, Payment, ArgumentMissingError, AlreadyRemindedPaymentError, \
NoPendingPaymentToUpdateError
from venmo_api import User, PaymentMethod, PaymentRole, PaymentPrivacy
from venmo_api import NoPaymentMethodFoundError
from venmo_api import deserialize, wrap_callback, get_user_id
Expand All @@ -7,9 +8,75 @@

class PaymentApi(object):

def __init__(self, api_client: ApiClient):
def __init__(self, profile, api_client: ApiClient):
super().__init__()
self.__profile = profile
self.__api_client = api_client
self.__payment_update_error_codes = {
"already_reminded_error": 2907,
"no_pending_payment_error": 2901,
"no_pending_payment_error2": 2905
}

def get_charge_payments(self, callback=None):
"""
Get a list of charge ongoing payments (pending request money)
:param callback:
:return:
"""
return self.__get_payments(action="charge",
callback=callback)

def get_pay_payments(self, callback=None):
"""
Get a list of pay ongoing payments (pending requested money from your profile)
:param callback:
:return:
"""
return self.__get_payments(action="pay",
callback=callback)

def remind_payment(self, payment: Payment = None, payment_id: int = None) -> bool:
"""
Send a reminder for payment/payment_id
:param payment: either payment object or payment_id must be be provided
:param payment_id:
:return: True or raises AlreadyRemindedPaymentError
"""

# if the reminder has already sent
payment_id = payment_id or payment.id
action = 'remind'

response = self.__update_payment(action=action,
payment_id=payment_id)

# if the reminder has already sent
if 'error' in response.get('body'):
if response['body']['error']['code'] == self.__payment_update_error_codes['no_pending_payment_error2']:
raise NoPendingPaymentToUpdateError(payment_id=payment_id,
action=action)
raise AlreadyRemindedPaymentError(payment_id=payment_id)
return True

def cancel_payment(self, payment: Payment = None, payment_id: int = None) -> bool:
"""
Cancel the payment/payment_id provided. Only applicable to payments you have access to (requested payments)
:param payment:
:param payment_id:
:return: True or raises NoPendingPaymentToCancelError
"""
# if the reminder has already sent
payment_id = payment_id or payment.id
action = 'cancel'

response = self.__update_payment(action=action,
payment_id=payment_id)

if 'error' in response.get('body'):
raise NoPendingPaymentToUpdateError(payment_id=payment_id,
action=action)
return True

def get_payment_methods(self, callback=None) -> Union[List[PaymentMethod], None]:
"""
Expand Down Expand Up @@ -85,6 +152,43 @@ def request_money(self, amount: float,
target_user=target_user,
callback=callback)

def __update_payment(self, action, payment_id):

if not payment_id:
raise ArgumentMissingError(arguments=('payment', 'payment_id'))

resource_path = f'/payments/{payment_id}'
body = {
"action": action,
}
return self.__api_client.call_api(resource_path=resource_path,
body=body,
method='PUT',
ok_error_codes=list(self.__payment_update_error_codes.values()))

def __get_payments(self, action, callback=None):
"""
Get a list of ongoing payments with the given action
:return:
"""
wrapped_callback = wrap_callback(callback=callback,
data_type=Payment)

resource_path = '/payments'
parameters = {
"action": action,
"actor": self.__profile.id,
"limit": 100000
}
response = self.__api_client.call_api(resource_path=resource_path,
params=parameters,
method='GET',
callback=wrapped_callback)
if callback:
return

return deserialize(response=response, data_type=Payment)

def __send_or_request_money(self, amount: float,
note: str,
is_send_money,
Expand Down
8 changes: 6 additions & 2 deletions venmo_api/apis/user_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,15 @@ class UserApi(object):
def __init__(self, api_client):
super().__init__()
self.__api_client = api_client
self.__profile = None

def get_my_profile(self, callback=None):
def get_my_profile(self, callback=None, force_update=False) -> Union[User, None]:
"""
Get my profile info and return as a <User>
:return my_profile: <User>
"""
if self.__profile and not force_update:
return self.__profile

# Prepare the request
resource_path = '/account'
Expand All @@ -31,7 +34,8 @@ def get_my_profile(self, callback=None):
if callback:
return

return deserialize(response=response, data_type=User, nested_response=nested_response)
self.__profile = deserialize(response=response, data_type=User, nested_response=nested_response)
return self.__profile

def search_for_users(self, query: str, callback=None,
page: int = 1, count: int = 50) -> Union[List[User], None]:
Expand Down
19 changes: 16 additions & 3 deletions venmo_api/models/exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ def __init__(self, response=None, msg: str = None):
except JSONDecodeError:
json = "Invalid Json"

self.msg = msg or f"HTTP Status code invalid. Could not make the request -> "\
f"{status_code} {reason}.\nJSON: {json}"
self.msg = msg or f"HTTP Status code is invalid. Could not make the request because -> "\
f"{status_code} {reason}.\nError: {json}"

super(HttpCodeError, self).__init__(self.msg)

Expand Down Expand Up @@ -73,6 +73,19 @@ def __init__(self, msg: str = None, reason=None):
super(NoPaymentMethodFoundError, self).__init__(self.msg)


class AlreadyRemindedPaymentError(Exception):
def __init__(self, payment_id: int):
self.msg = f"A reminder has already been sent to the recipient of this transaction: {payment_id}."
super(AlreadyRemindedPaymentError, self).__init__(self.msg)


class NoPendingPaymentToUpdateError(Exception):
def __init__(self, payment_id: int, action: str):
self.msg = f"There is no *pending* transaction with the specified id: {payment_id}, to be {action}ed."
super(NoPendingPaymentToUpdateError, self).__init__(self.msg)


__all__ = ["AuthenticationFailedError", "InvalidArgumentError", "InvalidHttpMethodError", "ArgumentMissingError",
"JSONDecodeError", "ResourceNotFoundError", "HttpCodeError", "NoPaymentMethodFoundError"
"JSONDecodeError", "ResourceNotFoundError", "HttpCodeError", "NoPaymentMethodFoundError",
"AlreadyRemindedPaymentError", "NoPendingPaymentToUpdateError"
]
64 changes: 64 additions & 0 deletions venmo_api/models/json_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ def user(json, is_profile=None):
def payment_method(json):
return PaymentMethodParser(json)

@staticmethod
def payment(json):
return PaymentParser(json)


class TransactionParser:

Expand Down Expand Up @@ -199,3 +203,63 @@ def get_payment_method_type(self):
'name': 'name',
'type': 'type'
}


class PaymentParser:

def __init__(self, json):
self.json = json

def get_id(self):
return self.json.get(payment_request_json_format['id'])

def get_actor(self):
return self.json.get(payment_request_json_format['actor'])

def get_target(self):
return self.json.get(payment_request_json_format['target'])\
.get(payment_request_json_format['target_user'])

def get_action(self):
return self.json.get(payment_request_json_format['action'])

def get_amount(self):
return self.json.get(payment_request_json_format['amount'])

def get_audience(self):
return self.json.get(payment_request_json_format['audience'])

def get_date_authorized(self):
return self.json.get(payment_request_json_format['date_authorized'])

def get_date_completed(self):
return self.json.get(payment_request_json_format['date_completed'])

def get_date_created(self):
return self.json.get(payment_request_json_format['date_created'])

def get_date_reminded(self):
return self.json.get(payment_request_json_format['date_reminded'])

def get_note(self):
return self.json.get(payment_request_json_format['note'])

def get_status(self):
return self.json.get(payment_request_json_format['status'])


payment_request_json_format = {
'id': 'id',
'actor': 'actor',
'target': 'target',
'target_user': 'user',
'action': 'action',
'amount': 'amount',
'audience': 'audience',
'date_authorized': 'date_authorized',
'date_completed': 'date_completed',
'date_created': 'date_created',
'date_reminded': 'date_reminded',
'note': 'note',
'status': 'status'
}
74 changes: 74 additions & 0 deletions venmo_api/models/payment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
from enum import Enum

from venmo_api import string_to_timestamp, User
from venmo_api import JSONSchema


class Payment(object):

def __init__(self, id_, actor, target, action, amount, audience, date_created, date_reminded, date_completed,
note, status):
"""
Create a Payment object
:param id_:
:param actor:
:param target:
:param action:
:param amount:
:param audience:
:param date_created:
:param date_reminded:
:param date_completed:
:param note:
:param status:
"""
super().__init__()
self.id = id_
self.actor = actor
self.target = target
self.action = action
self.amount = amount
self.audience = audience
self.date_created = date_created
self.date_reminded = date_reminded
self.date_completed = date_completed
self.note = note
self.status = status

@classmethod
def from_json(cls, json):
"""
init a new Payment form JSON
:param json:
:return:
"""
if not json:
return

parser = JSONSchema.payment(json)

return cls(
id_=parser.get_id(),
actor=User.from_json(parser.get_actor()),
target=User.from_json(parser.get_target()),
action=parser.get_action(),
amount=parser.get_amount(),
audience=parser.get_amount(),
date_created=string_to_timestamp(parser.get_date_created()),
date_reminded=string_to_timestamp(parser.get_date_reminded()),
date_completed=string_to_timestamp(parser.get_date_completed()),
note=parser.get_note(),
status=PaymentStatus(parser.get_status())
)

def __str__(self) -> str:
return '%s(%s)' % (
type(self).__name__,
', '.join('%s=%s' % item for item in vars(self).items())
)


class PaymentStatus(Enum):
SETTLED = 'settled'
CANCELLED = 'cancelled'
PENDING = 'pending'
2 changes: 1 addition & 1 deletion venmo_api/utils/api_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def validate_access_token(access_token):
return

if access_token[:6] != 'Bearer':
return f"Barear {access_token}"
return f"Bearer {access_token}"

return access_token

Expand Down

0 comments on commit e0e78de

Please sign in to comment.