import requests
from whmcspy import exceptions
def _is_inactive(obj, active):
"""
Check if the object is considered inactive.
This method is useful in filtering objects based on their state.
Args:
obj (dict): The WHMCS object to check.
active (bool): The active state to check. If set to True the
status should be Active, if set to False the status should not be
Active. If set to None no check occurs and all is not considered
inactive.
Returns:
True if the object is considered active.
"""
return (
active is True and obj['status'] != 'Active'
or active is False and obj['status'] == 'Active')
[docs]class WHMCS:
"""
WHMCS interface.
"""
def __init__(
self,
url,
identifier,
secret):
"""
Create a new instance.
Args:
url (str): The URL to the WHMCS api.
identifier (str): The identifier of the WHMCS credentials.
secret (str): The secret of the WHMCS credentials.
"""
self.url = url
self.identifier = identifier
self.secret = secret
def _format_array_params(self, params):
"""
Format lists as array params.
A list should be formatted in a certain way (PHP-ish?) in order to be
processable by WHMCS.
The params dict is modified to contain the new params.
Args:
params (dict): The params to process.
"""
for key, value in list(params.items()):
if isinstance(value, list):
for index, item in enumerate(value):
params[f'{key}[{index}]'] = item
del params[key]
[docs] def call(
self,
action,
**params):
"""
Call the WHMCS api.
This is an abstract way to call the WHMCS API. Basically only the
action and additional params are required to make a call.
Args:
action (str): The action to perform.
**params: Additional params.
Returns:
dict: The result of the call.
Raises:
MissingPermission: When access is denied due to a missing
permission.
Error: Whenever the call fails.
"""
payload = {
'identifier': self.identifier,
'secret': self.secret,
'action': action,
'responsetype': 'json',
}
self._format_array_params(params)
payload.update(params)
response = requests.post(
self.url,
verify=False,
data=payload)
response_ = response.json()
try:
result = response_['result']
except KeyError:
result = response_['status']
if result == 'error':
if response.status_code == 403:
raise exceptions.MissingPermission(response_['message'])
raise exceptions.Error(response_['message'])
return response_
[docs] def paginated_call(
self,
action,
limitstart=0,
**params):
"""
Perform a WHMCS API call, but paginated.
Instead of returning just a single result a result is yielded for
every iteration until an empty result returns from WHMCS.
See :func:`call` for common params.
Keyword Args:
limitstart (int): The offset from which to start. Initially this
is 0.
Yields:
An API response.
"""
while True:
params.update(
limitstart=limitstart,
)
response = self.call(
action,
**params)
if not response['numreturned']:
break
limitstart += response['numreturned']
yield response
[docs] def add_client(
self,
firstname,
lastname,
email,
address1,
city,
state,
postcode,
country,
phonenumber,
password2,
**params):
"""
Add client.
Args:
firstname (str): The client's firstname.
lastname (str): The client's lastname.
email (str): The client's email address.
address1 (str): The client's address.
city (str): The client's city.
state (str): The client's state.
postcode (str): The client's postcode.
country (str): 2 character ISO country code.
phonenumber (str): The client's phone number.
password2 (str): The client's password.
**params: Additional params.
Returns:
int: The id of the created client.
Hint:
For additional params, see the official API docs:
https://developers.whmcs.com/api-reference/addclient/
"""
result = self.call(
'AddClient',
firstname=firstname,
lastname=lastname,
email=email,
address1=address1,
city=city,
state=state,
postcode=postcode,
country=country,
phonenumber=phonenumber,
password2=password2,
**params)
return result['clientid']
[docs] def add_product(
self,
name,
gid,
**params):
"""
Add product.
Args:
name (str): The name of the product.
gid (int): The id of the group to add the product to.
**params: Additional params.
Returns:
int: The id of the created product (pid).
Hint:
For additional params, see the official API docs:
https://developers.whmcs.com/api-reference/addproduct/
"""
result = self.call(
'AddProduct',
name=name,
gid=gid,
**params)
return result['pid']
[docs] def get_orders(
self,
**params):
"""
Get orders.
Args:
**params: Additional params.
Yields:
The matching orders.
Hint:
For additional params, see the official API docs:
https://developers.whmcs.com/api-reference/getorders/
"""
for response in self.paginated_call(
'GetOrders',
**params):
for order in response['orders']['order']:
yield order
[docs] def get_servers(
self,
**params):
"""
Get servers configured in WHMCS.
Args:
**params: Additional params.
Hint:
For additional params, see the official API docs:
https://developers.whmcs.com/api-reference/getservers/
"""
response = self.call(
'GetServers',
**params)
return response['servers']
[docs] def get_tld_pricing(self):
"""
Get the TLD pricing.
Returns:
dict: The TLD pricing info.
"""
return self.call('GetTLDPricing')
[docs] def accept_order(
self,
order_id,
**params):
"""
Accept an order.
Args:
order_id (int): The id of the order to accept.
**params: Additional params.
Hint:
For additional params, see the official API docs:
https://developers.whmcs.com/api-reference/acceptorder/
"""
params.update(
orderid=order_id,
)
response = self.call(
'AcceptOrder',
**params)
return response
[docs] def add_order(
self,
clientid,
domains=None,
paymentmethod='banktransfer',
products=None,
**params):
"""
Add an order.
Args:
clientid (int): The id of the client whom the order is for.
**params: Additional params.
Keyword Args:
domains (list): A list of domains to include in the order.
paymentmethod (str): The payment method for the order.
products: A list of products (dict) with an id and a domain name
(`pid`, `domain`).
Returns:
The response of the successfully created order.
Hint:
For additional params, see the official API docs:
https://developers.whmcs.com/api-reference/addorder/
"""
params.update(
clientid=clientid,
paymentmethod=paymentmethod,
)
if domains:
for i, domain in enumerate(domains):
params[f'domain[{i}]'] = domain
params[f'domaintype[{i}]'] = 'register'
params[f'domainpriceoverride[{i}]'] = 0
params[f'domainrenewoverride[{i}]'] = 0
if products:
for i, product in enumerate(products):
params[f'pid[{i}]'] = product['id']
params[f'domain[{i}]'] = product['domain']
response = self.call(
'AddOrder',
**params)
return response
[docs] def add_transaction(
self,
paymentmethod,
**params):
"""
Add a transaction.
Args:
paymentmethod (str): The payment method used to perform the
transaction.
**params: Additional params.
Hint:
For additional params, see the official API docs:
https://developers.whmcs.com/api-reference/addtransaction/
"""
params.update(
paymentmethod=paymentmethod,
)
self.call(
'AddTransaction',
**params)
[docs] def cancel_order(
self,
orderid,
**params):
"""
Cancel a pending order.
Args:
orderid (int): The id of the order.
**params: Additional params.
Hint:
For additional params, see the official API docs:
https://developers.whmcs.com/api-reference/cancelorder/
"""
params.update(
orderid=orderid,
)
self.call(
'CancelOrder',
**params)
[docs] def delete_order(
self,
orderid,
**params):
"""
Delete a cancelled or fraud order.
Args:
orderid (int): The id of the order.
"""
params.update(
orderid=orderid,
)
self.call(
'DeleteOrder',
**params)
[docs] def get_clients_domains(
self,
active=None,
**params):
"""
Get domains (registrations).
Args:
**params: Additional params.
Keyword Args:
active (bool): Filter on active or inactive domains.
Yields:
The domains.
Hint:
For additional params, see the official API docs:
https://developers.whmcs.com/api-reference/getclientsdomains/
"""
for response in self.paginated_call(
'GetClientsDomains',
**params):
for domain in response['domains']['domain']:
if _is_inactive(domain, active):
continue
yield domain
[docs] def get_clients_products(
self,
active=None,
productid=None,
**params):
"""
Get client products.
Args:
**params: Additional params.
Keyword Args:
active (bool): Filter on active or inactive domains.
productid (int): Only get products with this product id.
Yields:
The products.
Hint:
For additional params, see the official API docs:
https://developers.whmcs.com/api-reference/getclientsproducts/
"""
if productid:
params['pid'] = productid
for response in self.paginated_call(
'GetClientsProducts',
**params):
for product in response['products']['product']:
if _is_inactive(product, active):
continue
yield product
[docs] def get_invoice(
self,
invoiceid):
"""
Get an invoice.
Args:
invoiceid (int): The id of the invoice.
Returns:
The invoice
"""
result = self.call(
'GetInvoice',
invoiceid=invoiceid)
return result
[docs] def get_tickets(
self,
**params):
"""
Get support tickets.
Args:
**params: Additional params.
Yields:
The tickets.
Hint:
For additional params, see the official API docs:
https://developers.whmcs.com/api-reference/gettickets/
"""
for response in self.paginated_call(
'GetTickets',
**params):
for ticket in response['tickets']['ticket']:
yield ticket
[docs] def get_transactions(
self,
**params):
"""
Get (find) transactions.
Args:
**params: Additional params.
Returns:
A list of matching transactions.
Hint:
For additional params, see the official API docs:
https://developers.whmcs.com/api-reference/gettransactions/
"""
response = self.call(
'GetTransactions',
**params)
return response.get('transactions', {}).get('transaction', [])
[docs] def module_create(
self,
serviceid):
"""
Run the module create action for a service.
Args:
serviceid (int): The id of the service.
"""
result = self.call(
'ModuleCreate',
serviceid=serviceid)
return result
[docs] def open_ticket(
self,
deptid,
subject,
message,
**params):
"""
Open a support ticket
Args:
deptid (int): The id of the department to open the ticket for.
subject (str): The subject of the ticket.
message (str): The initial message of the ticket.
**params: Additional params.
Hint:
For additional params, see the official API docs:
https://developers.whmcs.com/api-reference/openticket/
Note:
Markdown doesn't seem to work when opening a ticket by using
the API. Maybe it's fixed in later versions of WHMCS. Consider it
unstable.
"""
params.update(
deptid=deptid,
subject=subject,
message=message,
)
self.call(
'OpenTicket',
**params)
[docs] def pending_order(
self,
orderid,
**params):
"""
Set an order and it's items to Pending.
Args:
orderid (int): The id of the order.
"""
params.update(
orderid=orderid,
)
self.call(
'PendingOrder',
**params)
[docs] def send_email(
self,
**params):
"""
Send a client email notification.
Args:
**params: Additional params.
Hint:
For additional params, see the official API docs:
https://developers.whmcs.com/api-reference/sendemail/
"""
result = self.call(
'SendEmail',
**params)
[docs] def update_client_domain(
self,
domain,
**params):
"""
Update a client's domain registration.
Args:
domain (dict): The domain to update.
**params: Additional params.
Hint:
For additional params, see the official API docs:
https://developers.whmcs.com/api-reference/updateclientdomain/
"""
params.update(
domainid=domain['id'],
dnsmanagement=domain['dnsmanagement'],
emailforwarding=domain['emailforwarding'],
idprotection=domain['idprotection'],
donotrenew=domain['donotrenew'],
type=domain['regtype'],
regdate=domain['regdate'],
nextduedate=domain['nextduedate'],
expirydate=domain['expirydate'],
domain=domain['domainname'],
firstpaymentamount=domain['firstpaymentamount'],
recurringamount=domain['recurringamount'],
registrar=domain['registrar'],
regperiod=domain['regperiod'],
paymentmethod=domain['paymentmethodname'],
subscriptionid=domain['subscriptionid'],
status=domain['status'],
notes=domain['notes'],
promoid=domain['promoid'],
)
response = self.call(
'UpdateClientDomain',
**params)
return response
[docs] def update_client_product(
self,
productid,
**params):
"""
Update a client's product.
Args:
productid (int): The id of the client product.
**params: Additional params.
Hint:
For additional params, see the official API docs:
https://developers.whmcs.com/api-reference/updateclientproduct/
"""
params.update(
serviceid=productid,
)
nextduedate = params.get('nextduedate')
if nextduedate:
nextduedate = nextduedate.strftime('%Y-%m-%d')
response = self.call(
'updateClientProduct',
**params)
return response