Skip to content

Commit f5c8ca8

Browse files
committed
add subaccounts api
1 parent 4a27754 commit f5c8ca8

17 files changed

+861
-225
lines changed

subaccounts/README.md

+63-26
Original file line numberDiff line numberDiff line change
@@ -2,65 +2,102 @@
22

33
This package contains the code to use Vonage's Subaccount API in Python.
44

5-
It includes methods for managing Vonage subaccounts.
5+
It includes methods for creating and modifying Vonage subaccounts and transferring credit, balances and numbers between subaccounts.
66

77
## Usage
88

99
It is recommended to use this as part of the main `vonage` package. The examples below assume you've created an instance of the `vonage.Vonage` class called `vonage_client`.
1010

1111

12-
<!-- ### Get Account Balance
12+
### List Subaccounts
1313

1414
```python
15-
balance = vonage_client.account.get_balance()
16-
print(balance)
15+
response = vonage_client.subaccounts.list_subaccounts()
16+
print(response.model_dump)
1717
```
1818

19-
### Top-Up Account
19+
### Create Subaccount
2020

2121
```python
22-
response = vonage_client.account.top_up(trx='1234567890')
22+
from vonage_subaccounts import SubaccountOptions
23+
24+
response = vonage_client.subaccounts.create_subaccount(
25+
SubaccountOptions(
26+
name='test_subaccount', secret='1234asdfA', use_primary_account_balance=False
27+
)
28+
)
2329
print(response)
2430
```
2531

26-
### Update the Default SMS Webhook
27-
28-
This will return a Pydantic object (`SettingsResponse`) containing multiple settings for your account.
32+
### Modify a Subaccount
2933

3034
```python
31-
settings: SettingsResponse = vonage_client.account.update_default_sms_webhook(
32-
mo_callback_url='https://example.com/inbound_sms_webhook',
33-
dr_callback_url='https://example.com/delivery_receipt_webhook',
35+
from vonage_subaccounts import ModifySubaccountOptions
36+
37+
response = vonage_client.subaccounts.modify_subaccount(
38+
'test_subaccount',
39+
ModifySubaccountOptions(
40+
suspended=True,
41+
name='modified_test_subaccount',
42+
),
3443
)
44+
print(response)
45+
```
3546

36-
print(settings)
47+
### List Balance Transfers
48+
49+
```python
50+
from vonage_subaccounts import ListTransfersFilter
51+
52+
filter = {'start_date': '2023-08-07T10:50:44Z'}
53+
response = vonage_client.subaccounts.list_balance_transfers(ListTransfersFilter(**filter))
54+
for item in response:
55+
print(item.model_dump())
3756
```
3857

39-
### List Secrets Associated with the Account
58+
### Transfer Balance Between Subaccounts
4059

4160
```python
42-
response = vonage_client.account.list_secrets()
61+
from vonage_subaccounts import TransferRequest
62+
63+
request = TransferRequest(
64+
from_='test_api_key', to='test_subaccount', amount=0.02, reference='A reference'
65+
)
66+
response = vonage_client.subaccounts.transfer_balance(request)
4367
print(response)
4468
```
4569

46-
### Create a New Account Secret
70+
### List Credit Transfers
4771

4872
```python
49-
secret = vonage_client.account.create_secret('Mytestsecret12345')
50-
print(secret)
73+
from vonage_subaccounts import ListTransfersFilter
74+
75+
filter = {'start_date': '2023-08-07T10:50:44Z'}
76+
response = vonage_client.subaccounts.list_credit_transfers(ListTransfersFilter(**filter))
77+
for item in response:
78+
print(item.model_dump())
5179
```
5280

53-
### Get Information About One Secret
81+
### Transfer Credit Between Subaccounts
5482

5583
```python
56-
secret = vonage_client.account.get_secret(MY_SECRET_ID)
57-
print(secret)
58-
```
84+
from vonage_subaccounts import TransferRequest
5985

60-
### Revoke a Secret
86+
request = TransferRequest(
87+
from_='test_api_key', to='test_subaccount', amount=0.02, reference='A reference'
88+
)
89+
response = vonage_client.subaccounts.transfer_balance(request)
90+
print(response)
91+
```
6192

62-
Note: it isn't possible to revoke all account secrets, there must always be one valid secret. Attempting to do so will give a 403 error.
93+
### Transfer a Phone Number Between Subaccounts
6394

6495
```python
65-
client.account.revoke_secret(MY_SECRET_ID)
66-
``` -->
96+
from vonage_subaccounts import TransferNumberRequest
97+
98+
request = TransferNumberRequest(
99+
from_='test_api_key', to='test_subaccount', number='447700900000', country='GB'
100+
)
101+
response = vonage_client.subaccounts.transfer_number(request)
102+
print(response)
103+
```
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,35 @@
1+
from .errors import InvalidSecretError
2+
from .requests import (
3+
ListTransfersFilter,
4+
ModifySubaccountOptions,
5+
SubaccountOptions,
6+
TransferNumberRequest,
7+
TransferRequest,
8+
)
9+
from .responses import (
10+
ListSubaccountsResponse,
11+
NewSubaccount,
12+
PrimaryAccount,
13+
Subaccount,
14+
Transfer,
15+
TransferNumberResponse,
16+
VonageAccount,
17+
)
118
from .subaccounts import Subaccounts
219

320
__all__ = [
421
'Subaccounts',
22+
'InvalidSecretError',
23+
'ListTransfersFilter',
24+
'SubaccountOptions',
25+
'ModifySubaccountOptions',
26+
'TransferNumberRequest',
27+
'TransferRequest',
28+
'VonageAccount',
29+
'PrimaryAccount',
30+
'Subaccount',
31+
'ListSubaccountsResponse',
32+
'NewSubaccount',
33+
'Transfer',
34+
'TransferNumberResponse',
535
]
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from vonage_utils.errors import VonageError
22

33

4-
class SubaccountsError(VonageError):
5-
"""Indicates an error with the Subaccounts API package."""
4+
class InvalidSecretError(VonageError):
5+
"""Indicates that the secret provided was invalid."""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import re
2+
from typing import Optional
3+
4+
from pydantic import BaseModel, Field, field_validator
5+
from vonage_subaccounts.errors import InvalidSecretError
6+
7+
8+
class SubaccountOptions(BaseModel):
9+
name: str = Field(..., min_length=1, max_length=80)
10+
secret: Optional[str] = None
11+
use_primary_account_balance: Optional[bool] = None
12+
13+
@field_validator('secret')
14+
@classmethod
15+
def check_valid_secret(cls, v):
16+
if not _is_valid_secret(v):
17+
raise InvalidSecretError(
18+
'Secret must be 8-25 characters long and contain at least one uppercase '
19+
'letter, one lowercase letter, and one digit.'
20+
)
21+
return v
22+
23+
24+
def _is_valid_secret(secret: str) -> bool:
25+
if len(secret) < 8 or len(secret) > 25:
26+
return False
27+
if not re.search(r'[a-z]', secret):
28+
return False
29+
if not re.search(r'[A-Z]', secret):
30+
return False
31+
if not re.search(r'\d', secret):
32+
return False
33+
return True
34+
35+
36+
class ModifySubaccountOptions(BaseModel):
37+
suspended: Optional[bool] = None
38+
use_primary_account_balance: Optional[bool] = None
39+
name: Optional[str] = None
40+
41+
42+
class ListTransfersFilter(BaseModel):
43+
start_date: str
44+
end_date: Optional[str] = None
45+
subaccount: Optional[str] = None
46+
47+
48+
class TransferRequest(BaseModel):
49+
from_: str = Field(..., serialization_alias='from')
50+
to: str
51+
amount: float
52+
reference: Optional[str] = None
53+
54+
55+
class TransferNumberRequest(BaseModel):
56+
from_: str = Field(..., serialization_alias='from')
57+
to: str
58+
number: str
59+
country: Optional[str] = None
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,48 @@
1-
from datetime import datetime
2-
from typing import Optional
1+
from typing import Optional, Union
32

43
from pydantic import BaseModel, Field
54

65

76
class VonageAccount(BaseModel):
87
api_key: str
98
name: str
10-
primary_account_api_key: str
11-
use_primary_account_balance: bool
12-
created_at: datetime
9+
created_at: str
1310
suspended: bool
1411
balance: Optional[float]
15-
credit_limit: Optional[float]
12+
credit_limit: Optional[Union[int, float]]
13+
14+
15+
class PrimaryAccount(VonageAccount):
16+
...
17+
18+
19+
class Subaccount(VonageAccount):
20+
primary_account_api_key: str
21+
use_primary_account_balance: bool
22+
23+
24+
class ListSubaccountsResponse(BaseModel):
25+
primary_account: PrimaryAccount
26+
subaccounts: list[Subaccount]
27+
total_balance: float
28+
total_credit_limit: Union[int, float]
29+
30+
31+
class NewSubaccount(Subaccount):
32+
secret: str
1633

1734

18-
class PrimaryAccount(VonageAccount): ...
35+
class Transfer(BaseModel):
36+
id: str
37+
amount: float
38+
from_: str = Field(..., validation_alias='from')
39+
to: str
40+
created_at: str
41+
reference: Optional[str] = None
1942

2043

21-
class Subaccount(VonageAccount): ...
44+
class TransferNumberResponse(BaseModel):
45+
number: str
46+
country: str
47+
from_: str = Field(..., validation_alias='from')
48+
to: str

0 commit comments

Comments
 (0)