The purpose of this API is to give our clients direct access to their data so that they can better serve their clients. This API implements the project written by Maurits van der Schee released under the MIT License and hosted on GitHub at version 2.
PUT and POST calls to Read Only resources will result in the API returning a response code of 404.
In order to mitigate the possibility of accidental data loss the API implements a "soft delete" policy for the DELETE method. A call using the DELETE method will have the following result on the listed end points:
https://rjdeliveryomaha.com/v2/records/endpoint?Your-Query
The PHP funcion http-build-query should be used with the 'enc_type' flag of PHP_QUERY_RFC3986 to encode queries. The buildURI function gives an example of one way a query can be properly built.
Three special headers must be included with your requestData type errors will return a 422 response code.
{ "ticket_index": 1, "TicketNumber": 800910, "RunNumber": 0, ..., ... }If the query is made with filters, ex: GET /records/tickets?filter=ticket_index,le,5, or is a call to read an entire endpoint, ex: GET /records/tickets, the resulting object will have a single property of "records" which will be an array of objects matching the query parameters, ex:
{ "records": [ { "ticket_index": 1, "TicketNumber": 800910, "RunNumber": 0, ..., ... }, { "ticket_index": 2, "TicketNumber": 800911, "RunNumber": 0, ..., ... }, ..., ..., ... ] }If there is no match to the query the "records" property will be an empty array.
All non-numeric resources are collated utf8mb4_unicode_ci. The end points, resource names, and write permissions for the Courier Invoice API are as follows:
configInformation relating to processing and displaying tickets and invoices.
Resource | Data Type | Read Only | Null | Default | Description |
---|---|---|---|---|---|
Return To Top | |||||
config_index | int(11)(AI) | TRUE | PRIMARY KEY index used for updating via API | ||
user_index | int(11) | TRUE | NO | Foreign Key Constraint. References client_index of client 0 on the clients end point. | |
LogoFileName | varchar(11) | TRUE | NO | logo.* | File upload script renames files to "logo" with the appropriate file extension. |
CurrencySymbol | varchar(8) | FALSE | NO | ¤ | |
WeightsMeasures | tinyint(1) | FALSE | NO | 0 | 0 = Imperial, 1 = Metric |
InternationalAddressing | tinyint(1) | FALSE | NO | 0 | 0 = Hide country input and display, 1 = Show country input and display |
TimeZone | varchar(42) | FALSE | NO | UTC | Timezone string ex: America/North_Dakota/New_Salem Supported Timezones |
diPrice | decimal(6,2) | FALSE | NO | 0.00 | |
OneHour | float | FALSE | NO | 1 | |
TwoHour | float | FALSE | NO | 1 | |
ThreeHour | float | FALSE | NO | 1 | |
FourHour | float | FALSE | NO | 1 | |
DeadRun | float | FALSE | NO | 0 | |
DedicatedRunRate | float | FALSE | NO | 1 | |
ApplyVAT | tinyint(1) | FALSE | NO | 0 | bool. Indicates if value added tax options should be displayed. |
DefaultTerms | int(1) | FALSE | NO | 1 | Indicates the default terms for invoices. 1 = Due Upon Receipt, 2 = Net D, 3 = Net EOM D, 4 = A/B Net D. |
DiscountRate | decimal(4,2) | FALSE | NO | 0.00 | The percentage discount (A) offered for A/B Net D terms. |
DiscountWindow | int(3) | FALSE | NO | 0 | The number of days (B) a discount rate is offered for A/B Net D terms. |
TermLength | int(3) | FALSE | NO | 0 | The period of days (D) that terms are extended. |
InvoiceBy | int(1) | FALSE | NO | 0 | Date to invoice tickets by: 0 = ReceivedDate, 1 = ReadyDate, 2 = Compleation Date. |
MaximumFee | decimal(6,2) | FALSE | NO | 0 | A value of 0 (zero) indicates no maximum. |
Geocoders | text | FALSE | YES | NULL | |
BaseTicketFee | decimal(6,2) | FALSE | YES | NULL | |
RangeIncrement | tinyint(3) | FALSE | YES | NULL | |
PriceIncrement | float | FALSE | YES | NULL | |
MaxRange | decimal(7,2) | FALSE | YES | NULL | |
RangeCenter | varchar(23) | FALSE | YES | NULL | Latitude and Longitude of delivery range center. Ex: 41.2522201,-95.9822628 |
contract_locationsInformation relating to the locations for contract/repeating runs.
Resource | Data Type | Read Only | Null | Default | Description |
---|---|---|---|---|---|
Return To Top | |||||
cloc_index | int(11)(AI) | TRUE | PRIMARY KEY index used for updating via API | ||
Client | varchar(45) | FALSE | NO | None | |
Department | varchar(45) | FALSE | YES | NULL | |
Contact | varchar(45) | FALSE | YES | NULL | |
Telephone | varchar(20) | FALSE | YES | NULL | |
Address1 | varchar(45) | FALSE | NO | None | |
Address2 | varchar(45) | FALSE | NO | None | |
Country | varchar(2) | FALSE | NO | None | Country abbreviation generated with this function |
Deleted | tinyint(1) | FALSE | NO | 0 |
contract_runsDetails for contract/repeating runs.
Resource | Data Type | Read Only | Null | Default | Description |
---|---|---|---|---|---|
Return To Top | |||||
crun_index | int(11)(AI) | TRUE | PRIMARY KEY index used for updating via API | ||
RunNumber | int(11) | FALSE | NO | None | This value should be confirmed unique before submission. |
BillTo | int(11) | FALSE | NO | None | References clients |
pickup_id | int(11) | FALSE | NO | 1 | Foreign Key Constraint. References cloc_index on the contract_locations endpoint. This value is returned when a location is succfully entered via the API, an array of values is returned if locations are batch entered. |
dropoff_id | int(11) | FALSE | NO | 1 | Foreign Key Constraint. References cloc_index on the contract_locations endpoint. This value is returned when a location is succfully entered via the API, an array of values is returned if locations are batch entered. |
RoundTrip | tinyint(1) | FALSE | NO | 0 | bool |
pTime | time | FALSE | YES | NULL | |
dTime | time | FALSE | YES | NULL | |
d2Time | time | FALSE | YES | NULL | |
pSigReq | tinyint(1) | FALSE | NO | 0 | Request signature on pick up. |
dSigReq | tinyint(1) | FALSE | NO | 0 | Request signature on delivery. |
d2SigReq | tinyint(1) | FALSE | NO | 0 | Request signature on return. |
StartDate | date | FALSE | NO | None | |
LastCompleted | date | FALSE | YES | NULL | Used for scheduling |
DryIce | tinyint(1) | FALSE | NO | 0 | bool |
diWeight | decimal(6,3) | FALSE | NO | 0.000 | |
Notes | text | FALSE | YES | NULL | |
PriceOverride | tinyint(1) | FALSE | NO | 0 | bool |
VATable | tinyint(1) | FALSE | NO | 0 | bool. Is this ticket subject to VAT. |
VATtype | int(1) | FALSE | NO | 0 | 0 = Not VAT-able, 1 = Standard, 2 = Reduced, 3 = Client Standard, 4 = Client Reduced, 5 = Zero-Rated, 6 = Exempt |
VATableIce | tinyint(1) | FALSE | NO | 0 | bool. Is the dry ice on this ticket subject to VAT. |
VATtypeIce | int(1) | FALSE | NO | 0 | 0 = Not VAT-able, 1 = Standard, 2 = Reduced, 3 = Client Standard, 4 = Client Reduced, 5 = Zero-Rated, 6 = Exempt |
TicketPrice | decimal(6,2) | FALSE | Yes | NULL |
c_run_schedule Schedule information for contract runs
Resource | Data Type | Read Only | Null | Default | Description |
---|---|---|---|---|---|
Return To Top | |||||
crs_index | int(11)(AI) | TRUE | PRIMARY KEY index used for updating via API | ||
crun_index | int(11) | FALSE | FALSE | 1 | Foreign Key Constraint. References crun_index on the contract_runs end point. |
schedule_index | int(11) | FALSE | FALSE | 1 | Foreign Key Constraint. References the schedule end point. |
routesDetails routes grouping contract runs.
Resource | Data Type | Read Only | Null | Default | Description |
---|---|---|---|---|---|
Return To Top | |||||
route_index | int(11)(AI) | TRUE | PRIMARY KEY index used for updating via API | ||
RouteName | varchar(40) | FALSE | No | New Route | User assigned value to identify routes individually. |
driver_index | int(11) | FALSE | No | 0 | Foreign Key Constraint. References driver_index on the drivers endpoint. 1 = Not Dispatched. |
Overnight | tinyint(1) | FALSE | No | 0 | bool. If 0 the route will be treated as though it begins in the morning and ends in the evening with early hours preceding later ones. If 1 the route will be treated as though it begins in the evening and ends in the morning with early hours following later ones. |
LastDispatched | datetime | FALSE | Yes | null | Date and time, Y-m-d h:i:s, that the route was last dispatched. |
StartTime | time | FALSE | Yes | null | The earliest time, hh:ii:ss, that the route can be dispatched. |
StartDate | date | FALSE | Yes | null | The first date to apply the route. Null should be interpreted as the first time the route is tested. |
route_schedule Schedule information for routes
Resource | Data Type | Read Only | Null | Default | Description |
---|---|---|---|---|---|
Return To Top | |||||
rts_index | int(11)(AI) | TRUE | PRIMARY KEY index used for updating via API | ||
route_index | int(11) | FALSE | FALSE | 1 | Foreign Key Constraint. References crun_index on the routes end point. |
schedule_index | int(11) | FALSE | FALSE | 1 | Foreign Key Constraint. References the schedule end point. |
route_tickets Reference table linking routes and contract_runs
Resource | Data Type | Read Only | Null | Default | Description |
---|---|---|---|---|---|
Return To Top | |||||
rt_index | int(11)(AI) | TRUE | PRIMARY KEY index used for updating via API | ||
crun_index | int(11) | FALSE | FALSE | 1 | Foreign Key Constraint. References crun_index on the contract_runs endpoint. |
route_index | int(11) | FALSE | FALSE | 1 | Foreign Key Constraint. References route_index on the routes endpoint. |
schedule Scheduling codes.
Note: the Scheduling class included in our project on github makes calls to this endpoint unnecessary.
Resource | Data Type | Read Only | Null | Default | Description |
---|---|---|---|---|---|
Return To Top | |||||
schedule_index | int(11) | TRUE | PRIMARY KEY index used for referencing schedule codes and literals. | ||
code | varchar(3) | TRUE | 2 or 3 character scheduling code. [a-g][1-9] or h5 - h28 | ||
literal | varchar(25) | TRUE | Literal schedule. Ex: Every Thursday or Every Last Weekday or Every 17th |
dispatchers Information relating to dispatchers.
Resource | Data Type | Read Only | Null | Default | Description |
---|---|---|---|---|---|
Return To Top | |||||
dispatch_index | int(11)(AI) | TRUE | PRIMARY KEY index used for updating via API | ||
DispatchID | int(11) | FALSE | NO | None | This values should be confirmed unique before submission. |
FirstName | varchar(20) | FALSE | NO | Default Name | |
LastName | varchar(20) | FALSE | YES | NULL | |
EmailAddress | text | FALSE | YES | NULL | |
Password | varchar(255) | FALSE | NO | None | Hash only. No raw passwords should be stored in the database |
LoggedIn | tinyint(1) | FALSE | NO | 0 | bool |
LastSeen | datetime | FALSE | YES | NULL | |
Deleted | tinyint(1) | FALSE | NO | 0 | bool |
driversInformation relating to drivers.
Resource | Data Type | Read Only | Null | Default | Description |
---|---|---|---|---|---|
Return To Top | driver_index | int(11)(AI) | TRUE | PRIMARY KEY index used for updating via API | |
DriverID | int(11) | FALSE | NO | None | This value should be confirmed unique before submission. |
FirstName | varchar(20) | FALSE | NO | Default Name | |
LastName | varchar(20) | FALSE | YES | NULL | |
EmailAddress | varchar(254) | FALSE | YES | NULL | |
Password | varchar(255) | FALSE | NO | None | Hash only. No raw passwords should stored in the database |
LoggedIn | tinyint(1) | FALSE | NO | 0 | bool |
LastSeen | datetime | FALSE | YES | NULL | |
CanDispatch | int(1) | FALSE | NO | 0 | Describes to whom the driver can dispatch: 0 = None, 1 = Self, 2 = Any |
Deleted | tinyint(1) | FALSE | NO | 0 | bool |
clientsInformation relating to repeat clients.
Resource | Data Type | Read Only | Null | Default | Description |
---|---|---|---|---|---|
Return To Top | |||||
client_index | int(11)(AI) | TRUE | PRIMARY KEY index used for updating via API | ||
RepeatClient | tinyint(1) | FALSE | NO | 1 | bool: 0 = Non-Repeat Client, 1 = Repeat Client |
ClientID | int(11) | FALSE | NO | None | This value should be confirmed unique before submission. |
ClientName | varchar(45) | FALSE | NO | Default Name | |
Department | varchar(45) | FALSE | YES | NULL | |
ShippingAddress1 | varchar(45) | FALSE | NO | None | |
ShippingAddress2 | varchar(45) | FALSE | NO | None | |
ShippingCountry | varchar(2) | FALSE | NO | None | Country abbreviation generated with this function |
BillingName | varchar(45) | FALSE | Yes | NULL | |
BillingAddress1 | varchar(45) | FALSE | Yes | NULL | |
BillingAddress2 | varchar(45) | FALSE | Yes | NULL | |
BillingCountry | varchar(2) | FALSE | Yes | NULL | Country abbreviation generated with this function |
Same | tinyint(1) | FALSE | No | 0 | bool. Indicates if shipping and billing name and address are the same. |
Telephone | varchar(20) | FALSE | YES | NULL | |
EmailAddress | varchar(254) | FALSE | YES | NULL | |
Attention | varchar(45) | FALSE | YES | NULL | |
ContractDiscount | decimal(5,2) | FALSE | NO | 0.00 | |
GeneralDiscount | decimal(5,2) | FALSE | NO | 0.00 | |
StandardVAT | decimal(4,2) | FALSE | NO | 0.00 | Standard rate for VAT on tickets. |
ReducedVAT | decimal(4,2) | FALSE | NO | 0.00 | Reduced rate for VAT on tickets. |
VATtype | int(1) | FALSE | NO | 0 | 0 = Not VAT-able, 1 = Client 0 Standard, 2 = Client 0 Reduced, 3 = This Client Standard, 4 = This Client Reduced, 5 = Zero-Rated, 6 = Exempt |
VATtypeIce | int(1) | FALSE | NO | 0 | 0 = Not VAT-able, 1 = Client 0 Standard, 2 = Client 0 Reduced, 3 = This Client Standard, 4 = This Client Reduced, 5 = Zero-Rated, 6 = Exempt |
DIPO | tinyint(1) | FALSE | NO | 0 | bool. Indicates that the client has a dry ice price different from config. |
DIPrice | decimal(5,2) | FALSE | NO | 0 | Alternate dry ice price for client. |
ClientTerms | int(1) | FALSE | NO | 0 | Indicates terms for each client. 0 = Default, 1 = Due Upon Receipt, 2 = Net D, 3 = Net EOM D, 4 = A/B Net D. |
DiscountRate | decimal(4,2) | FALSE | NO | 0.00 | The percentage discount (A) offered for A/B Net D terms. |
DiscountWindow | int(3) | FALSE | NO | 0 | The number of days (B) a discount rate is offered for A/B Net D terms. |
TermLength | int(3) | FALSE | NO | 0 | The period of days (D) that terms are extended. |
EmailInvoice | tinyint(1) | FALSE | NO | 0 | bool: Shoud invoices be delivered vai email or other method. |
Organization | int(11) | FALSE | NO | 0 | References the ID field of the o_clients end point |
org_id | int(11) | FLASE | NO | 1 | Foreign Key Constraint. References o_client_index on the o_clients endpoint. This value is returned when an organization is succfully entered via the API, an array of values is returned if organizations are batch entered. |
Deleted | tinyint(1) | FALSE | NO | 0 | bool |
Password | varchar(255) | FALSE | NO | Hash of default password | Hash only. No raw passwords should be stored in the database |
AdminPassword | varchar(255) | FALSE | NO | Hash of default password | Hash only. No raw passwords should be stored in the database |
o_clientsInformation on clients grouped together as an organization.
Resource | Data Type | Read Only | Null | Default | Description |
---|---|---|---|---|---|
Return To Top | |||||
o_client_index | int(11)(AI) | TRUE | PRIMARY KEY index used for updating via API | ||
id | int(11) | FALSE | NO | None | This value should be confirmed unique before submission. |
Name | varchar(40) | FALSE | NO | Default Name | |
Login | varchar(40) | FALSE | NO | default | |
Password | varchar(255) | FALSE | NO | Hash of default password | Hash only. No raw passwords should be stored in the database |
ListBy | int(1) | FALSE | NO | 0 | 0 = Street Address, 1 = Department |
RequestTickets | int(1) | FALSE | NO | 0 | 0 = No ticket requesting. 1 = Members can bill to other members. 2 = Org can request tickets. >=3 = 1 + 2 |
Deleted | tinyint(1) | FALSE | NO | 0 | bool |
schedule_overrideInformation relating to exceptions to contract/repeating run scheduling.
Resource | Data Type | Read Only | Null | Default | Description |
---|---|---|---|---|---|
Return To Top | |||||
s_o_index | int(11)(AI) | TRUE | PRIMARY KEY index used for updating via API | ||
crun_index | int(11) | FALSE | NO | 1 | Foreign Key Constraint. References crun_index on the contract_runs endpoint. This value is returned when a run is succfully entered via the API, an array of values is returned if runs are batch entered. The Cancel All Runs event uses crun_index 1. |
route_index | int(11) | FALSE | NO | 1 | Foreign Key Constraint. References route_index on the routes endpoint. This value is returned when a route is succfully entered via the API, an array of values is returned if runs are batch entered. Cancelations use route_index 1. |
Cancel | tinyint(1) | FALSE | NO | 1 | bool: 0 = reschedule run at crun_index and put it on route at route_index, 1 = cancel run at crun_index. |
StartDate | date | FALSE | NO | None | |
EndDate | date | FALSE | NO | None | |
pTime | time | FALSE | NO | 00:00:00 | New Pick Up time if Cancel = 0 |
dTime | time | FALSE | NO | 00:00:00 | New Drop Off time if Cancel = 0 |
d2Time | time | FALSE | YES | NULL | New Return Time if Cancel = 0 |
Deleted | tinyint(1) | FALSE | NO | 0 | bool |
ticketsInformation relating to tickets.
Resource | Data Type | Read Only | Null | Default | Description |
---|---|---|---|---|---|
Return To Top | |||||
ticket_index | int(11)(AI) | TRUE | PRIMARY KEY index used for updating via API | ||
TicketNumber | bigint(20) | FALSE | NO | 0 | This value should be confirmed unique before submission. |
RunNumber | int(11) | FALSE | NO | 0 | References contract_runs |
BillTo | int(11) | FALSE | NO | 0 | References clients. |
RepeatClient | tinyint(1) | FALSE | NO | 1 | bool: Refers to the value of the RepeatClient field in the clients table. |
RequestedBy | varchar(45) | FALSE | YES | NULL | |
pClient | varchar(45) | FALSE | NO | Pick Up Client | Pick up location name |
pDepartment | varchar(45) | FALSE | YES | NULL | Pick up location department |
pContact | varchar(45) | FALSE | YES | NULL | Pick up location contact |
pTelephone | varchar(20) | FALSE | YES | NULL | Pick up location contact telephone |
pAddress1 | varchar(45) | FALSE | NO | - | Pick up building number, street, room number |
pAddress2 | varchar(45) | FALSE | NO | - | Picket up city/town, state/province, zip/post code |
pCountry | varchar(2) | FALSE | NO | - | Pick up country abbreviation generated with this function |
dClient | varchar(45) | FALSE | NO | Delivery Client | Delivery location name |
dDepartment | varchar(45) | FALSE | YES | NULL | Delivery location department |
dContact | varchar(45) | FALSE | YES | NULL | Delivery location contact |
dTelephone | varchar(20) | FALSE | YES | NULL | Delivery location contact telephone |
dAddress1 | varchar(45) | FALSE | NO | - | Delivery building number, street, room number |
dAddress2 | varchar(45) | FALSE | NO | - | Delivery city/town, state/province, zip/post code |
dCountry | varchar(2) | FALSE | NO | - | Delivery country abbreviation generated with this function |
dryIce | tinyint(1) | FALSE | NO | 0 | bool: 0 = No Dry Ice, 1 = Dry Ice |
diWeight | decimal(6,3) | FALSE | NO | 0.00 | Total weight of dry ice included with the delivery |
diPrice | decimal(6,2) | FALSE | NO | 0.00 | Total charge for dry ice included with the delivery |
TicketBase | decimal(6,2) | FALSE | NO | 0.00 | Unmodified ticket price |
Charge | int(1) | FALSE | NO | 5 | 0 = Canceled, 1 = 1 hour, 2 = 2 hour, 3 = 3 hour, 4 = 4 hour, 5 = Routine, 6 = Round Trip, 7 = Dedicated Run, 8 = Dead Run, 9 = Credit |
Contract | tinyint(1) | FALSE | NO | 0 | bool: 0 = On Call, 1 = Contract |
Multiplier | float | FALSE | NO | 1 | |
RunPrice | decimal(6,2) | FALSE | NO | 0.00 | Ticket price after modification by Charge, before modification by VATrate or Multiplier. |
VATable | tinyint(1) | FALSE | NO | 0 | bool. Is this ticket subject to VAT. |
VATrate | decimal(4,2) | FALSE | NO | 0.00 | The percentage rate of applicable VAT. |
VATtype | int(1) | FALSE | NO | 0 | 0 = Not VAT-able, 1 = Standard, 2 = Reduced, 3 = Client Standard, 4 = Client Reduced, 5 = Zero-Rated, 6 = Exempt |
VATableIce | tinyint(1) | FALSE | NO | 0 | bool. Is the dry ice on this ticket subject to VAT. |
VATrateIce | decimal(4,2) | FALSE | NO | 0.00 | The percentage rate of applicable VAT. |
VATtypeIce | int(1) | FALSE | NO | 0 | 0 = Not VAT-able, 1 = Standard, 2 = Reduced, 3 = Client Standard, 4 = Client Reduced, 5 = Zero-Rated, 6 = Exempt |
TicketPrice | decimal(6,2) | FALSE | NO | 0.00 | Final ticket price after Charge modification, dry ice addition, Multiplier, and applicable VAT. |
Notes | text | FALSE | YES | NULL | |
Telephone | varchar(20) | FALSE | YES | NULL | Telephone number of billed client |
EmailConfirm | int(1) | FALSE | NO | 0 | 0 = none, 1 = On Pick Up, 2 = On Delivery, 3 = On Pick Up and On Delivery, 4 = On Return, 5 = On Pick Up and On Return, 6 = On Delivery and On Return, 7 = At Each Step |
EmailAddress | varchar(255) | FALSE | YES | NULL | |
pSigReq | tinyint(1) | FALSE | NO | 0 | Request signature on pick up. |
pSigPrint | varchar(45) | FALSE | YES | NULL | |
pSig | mediumblob | FALSE | YES | NULL | For storing captured image of signature. png or jpg format. Maximum size of (2^24) - 1 bytes (16MB) |
pSigType | varchar(4) | FALSE | YES | NULL | Identifies the file extension of binary data stored in pSig without a dot. See image_type_to_extension |
dSigReq | tinyint(1) | FALSE | NO | 0 | Request signature on delivery. |
dSigPrint | varchar(45) | FALSE | YES | NULL | |
dSig | mediumblob | FALSE | YES | NULL | For storing captured image of signature. png or jpg format. Maximum size of (2^24) - 1 bytes (16MB) |
dSigType | varchar(4) | FALSE | YES | NULL | Identifies the file extension of binary data stored in dSig without a dot. See image_type_to_extension |
d2SigReq | tinyint(1) | FALSE | NO | 0 | Request signature on return. |
d2SigPrint | varchar(45) | FALSE | YES | NULL | |
d2Sig | mediumblob | FALSE | YES | NULL | For storing captured image of signature. png or jpg format. Maximum size of (2^24) - 1 bytes (16MB) |
d2SigType | varchar(4) | FALSE | YES | NULL | Identifies the file extension of binary data stored in d2Sig without a dot. See image_type_to_extension |
pSigFileName | varchar(256) | FALSE | YES | NULL | Name of pick up signature file. |
dSigFileName | varchar(256) | FALSE | YES | NULL | Name of delivery signature file. |
d2SigFileName | varchar(256) | FALSE | YES | NULL | Name of return signature file. |
NotForDispatch | tinyint(1) | FALSE | NO | 0 | If set to 1 the API should not return this ticket to drivers or dispatchers. |
DispatchTimeStamp | datetime | FALSE | YES | NULL | |
DispatchMicroTime | varchar(7) | FALSE | NO | .0 | String representation of the mircotime created by parsing microtime(). Ex: .123456 |
ReadyDate | datetime | FALSE | YES | NULL | If a delivery is not ready for pick up when a request is made this value can be used to indicate this to a driver. |
DispatchedTo | int(11) | FALSE | NO | 0 | References drivers |
DispatchedBy | varchar(7) | FALSE | NO | 1.1 |
Coded representation of who dispatched a ticket, ex: 1.2. Left of the dot refers to the level of the user 1 = dispatcher, 2 = driver. Right of the dot refers to the dispatcher / driver ID. |
ReceivedDate | datetime | FALSE | NO | None | |
Transfers | longtext | FALSE | YES | NULL | json encoded string representing ticket transfers. This should be a numeric array containing objects. Child objects should have only the following properties:
"holder": (number) ID of driver who had the ticket originally, "receiver": (number) ID of driver receiving the ticket, "transferedBy": (string) ID of driver / dispatcher transferring the ticket (see DispatchedBy), "timestamp": (number) Unix timestamp. Ex: [ { "holder":1, "receiver":2, "transferedBy":"2.1", "timestamp":1512574403296 }, { "holder":2, "receiver":1, "transferedBy":"2.2", "timestamp":1512574797926 } ] |
TransferState | tinyint(1) | FALSE | NO | 0 | Describes the current state of a transfer. 0 = inactive, 1 = pending |
PendingReceiver | int(11) | FALSE | NO | 0 | Driver ID of the target receiver of the current transfer. |
pTimeStamp | datetime | FALSE | YES | NULL | |
dTimeStamp | datetime | FALSE | YES | NULL | |
d2TimeStamp | datetime | FALSE | YES | NULL | |
pLat | decimal(8,6) | FALSE | YES | NULL | Pick up location latitude |
pLng | decimal(9,6) | FALSE | YES | NULL | Pick up location longitude |
dLat | decimal(8,6) | FALSE | YES | NULL | Delivery location latitude |
dLng | decimal(9,6) | FALSE | YES | NULL | Delivery location longitude |
d2Lat | decimal(8,6) | FALSE | YES | NULL | Return location latitude |
d2Lng | decimal(9,6) | FALSE | YES | NULL | Return location longitude |
pTime | time | FALSE | YES | NULL | Used for scheduling |
dTime | time | FALSE | YES | NULL | Used for scheduling |
d2Time | time | FALSE | YES | NULL | Used for scheduling |
invoice_id | int(11) | FALSE | NO | 1 | Foreign Key Constraint. References invoice_index on the invoices endpoint. This value is returned when an invoice is succfully entered via the API, an array of values is returned if invoices are batch entered. |
InvoiceNumber | varchar(15) | FALSE | NO | - | Indicates what invoice, if any, the ticket is billed on. Expects an invoice number in one of two formats: 1: ##EX####-## regex: /(^[\d]{2}EX[\d]+-[\d]+$)/ 2: ##EX####-t## regex: /(^[\d]{2}EX[\d]+-t[\d]+$)/ |
invoicesInformation relating to invoices.
Resource | Data Type | Read Only | Null | Default | Description |
---|---|---|---|---|---|
Return To Top | |||||
invoice_index | int(11)(AI) | TRUE | PRIMARY KEY index used for updating via API. | ||
InvoiceNumber | varchar(15) | FALSE | NO | 00EX0000-0 | This value should be confirmed unique before submission. Expects an invoice number in one of two formats: 1: ##EX####-## regex: /(^[\d]{2}EX[\d]+-[\d]+$)/ 2: ##EX####-t## regex: /(^[\d]{2}EX[\d]+-t[\d]+$)/ The presence of a 't' after the dash indicates RepeatClient value of 0. |
InvoiceTerms | int(1) | FALSE | NO | 1 | Indicates the terms an invoice. 1 = Due Upon Receipt, 2 = Net D, 3 = Net EOM D, 4 = A/B Net D. |
DiscountRate | decimal(4,2) | FALSE | NO | 0.00 | The percentage discount (A) offered for A/B Net D terms. |
DiscountWindow | int(3) | FALSE | NO | 0 | The number of days (B) a discount rate is offered for A/B Net D terms. |
TermLength | int(3) | FALSE | NO | 0 | The period of days (D) that terms are extended. |
ClientID | int(11) | FALSE | NO | 0 | |
RepeatClient | tinyint(1) | FALSE | NO | 1 | Reference RepeatClient field of the clients table. |
BalanceForwarded | decimal(6,2) | FALSE | NO | 0.00 | This value is taken from the Balance field of the most recently closed invoice. |
InvoiceSubTotal | decimal(6,2) | FALSE | NO | 0.00 | This value is the sum of all tickets included on the invoice. |
AmountDue | decimal(6,2) | FALSE | NO | 0.00 | This value is calculated when an invoice is closed and is the difference between InvoiceSubTotal and BalanceForwarded. |
InvoiceTotal | decimal(6,2) | FALSE | NO | 0.00 | This value is the sum of InvoiceSubTotal, BalanceForwarded, and any past due invoices. |
StartDate | date | FALSE | NO | None | |
EndDate | date | FALSE | NO | None | |
DateIssued | date | FALSE | NO | None | |
DatePaid | date | FALSE | YES | NULL | |
AmountPaid | decimal(6,2) | FALSE | NO | 0.00 | |
Balance | decimal(6,2) | FALSE | NO | 0.00 | This value is calculated when an invoice is closed and is the difference between AmountDue and AmountPaid. |
Late30Invoice | varchar(16) | FALSE | YES | NULL | Identifies Invoice(s) that are 30 days past due at the time of current invoice creation. Expects an invoice number in one of four formats: 1: ##EX####-## regex: /(^[\d]{2}EX[\d]+-[\d]+$)/ 2: ##EX####-t## regex: /(^[\d]{2}EX[\d]+-t[\d]+$)/ 3: ##EX####-##+ regex: /(^[\d]{2}EX[\d]+-[\d]+\+$)/ 4: ##EX####-t##+ regex: /(^[\d]{2}EX[\d]+-t[\d]+\+$)/ |
Late30Value | decimal(8,2) | FALSE | YES | NULL | Sum of InvoiceSubTotal of Invoice(s) that are 30 days past due at the time of current invoice creation |
Late60Invoice | varchar(16) | FALSE | YES | NULL | Identifies Invoice(s) that are 60 days past due at the time of current invoice creation. Expects an invoice number in one of four formats: 1: ##EX####-## regex: /(^[\d]{2}EX[\d]+-[\d]+$)/ 2: ##EX####-t## regex: /(^[\d]{2}EX[\d]+-t[\d]+$)/ 3: ##EX####-##+ regex: /(^[\d]{2}EX[\d]+-[\d]+\+$)/ 4: ##EX####-t##+ regex: /(^[\d]{2}EX[\d]+-t[\d]+\+$)/ |
Late60Value | decimal(8,2) | FALSE | YES | NULL | Sum of InvoiceSubTotal of Invoice(s) that are 60 days past due at the time of current invoice creation |
Late90Invoice | varchar(16) | FALSE | YES | NULL | Identifies Invoice(s) that are 90 days past due at the time of current invoice creation. Expects an invoice number in one of four formats: 1: ##EX####-## regex: /(^[\d]{2}EX[\d]+-[\d]+$)/ 2: ##EX####-t## regex: /(^[\d]{2}EX[\d]+-t[\d]+$)/ 3: ##EX####-##+ regex: /(^[\d]{2}EX[\d]+-[\d]+\+$)/ 4: ##EX####-t##+ regex: /(^[\d]{2}EX[\d]+-t[\d]+\+$)/ |
Late90Value | decimal(8,2) | FALSE | YES | NULL | Sum of InvoiceSubTotal of Invoice(s) that are 90 days past due at the time of current invoice creation. |
Over90Invoice | varchar(50) | FALSE | YES | NULL | Identifies Invoice(s) that are more than 90 days past due at the time of current invoice creation. Expects either a comma separated list of up to 4 Invoice Numbers as described on the InvoiceNumber end point or a comma separated list of three such Invoice Numbers followed by a plus symbol (+) an integer and the word 'more'. The last case should look like this (the letter 't' should follow the dash (-) in the case of non repeat clients): ##EX####-##, ##EX####-##, ##EX####-##, + ### more. Note the comma between the last invoice number and the plus symbol (+), if this is omitted data validation will fail. |
Over90Value | decimal(8,2) | FALSE | YES | NULL | Sum of InvoiceSubTotal of Invoice(s) that are more than 90 days past due at the time of current invoice creation |
PaymentLink | varchar(256) | FALSE | YES | NULL | Link for online payment. |
CheckNumber | varchar(50) | FALSE | YES | NULL | Check number, transaction number, or any other identifier for payment method. |
Closed | tinyint(1) | FALSE | NO | 0 | bool |
Deleted | tinyint(1) | FALSE | NO | 0 | bool |
webhooksInformation relating to webhook listeners.
Resource | Data Type | Read Only | Null | Default | Description |
---|---|---|---|---|---|
Note: Currently the POST method is disabled for this endpoint. | |||||
Return To Top | |||||
webhook_index | int(11)AI | TRUE | NO | None | PRIMARY KEY index used for updateing via API. |
Protocol | tinyint(1) | FALSE | NO | 1 | bool: 0 = http, 1 = https |
Listener | varchar(255) | FALSE | NO | None | URL listening for webhooks. |
Events | text | FALSE | NO | None | Comma-separated list of dot-notated events. Ex: ticket.oncall.receive,ticket.oncall.dispatch (Note: There are no white-spaces in this string). See Webhooks page for full list of values. |
Deleted | tinyint(1) | FALSE | NO | 0 | Boolean indicating if the webhook has been deactivated. |
<?php function countryFromAbbr($abbr) { // Credits will have a value of '-' for pCountry and dCountry if ($abbr === '-') return $abbr; //Country names and abbreviations taken from FedEx international shipping guidelines //"XZ" is not on the abbreviation list so it will stand for "Not On File" if (strlen($abbr) === 2) { switch ($abbr) { case 'AL': return 'Albania'; case 'DZ': return 'Algeria'; case 'AD': return 'Andorra'; case 'AO': return 'Angola'; case 'AR': return 'Argentina'; case 'AM': return 'Armenia'; case 'AW': return 'Aruba'; case 'AU': return 'Australia'; case 'AT': return 'Austria'; case 'AZ': return 'Azerbaijan'; case 'PT': return 'Azores'; case 'BS': return 'Bahamas'; case 'BH': return 'Bahrain'; case 'BD': return 'Bangladesh'; case 'BB': return 'Barbados'; case 'BY': return 'Belarus'; case 'BE': return 'Belgium'; case 'BZ': return 'Belize'; case 'BJ': return 'Benin'; case 'BM': return 'Bermuda'; case 'BT': return 'Bhutan'; case 'BO': return 'Bolivia'; case 'BA': return 'Bosna-Herzegovina'; case 'BW': return 'Botswana'; case 'BR': return 'Brazil'; case 'BN': return 'Brunei Darussalam'; case 'BG': return 'Bulgaria'; case 'BF': return 'Burkina FASO'; case 'BI': return 'Burundi'; case 'KH': return 'Cambodia'; case 'CM': return 'Cameroon'; case 'CA': return 'Canada'; case 'CV': return 'Cape Verde'; case 'KY': return 'Cayman Islands'; case 'CF': return 'Cntl African Republic'; case 'TS': return 'Chad'; case 'CL': return 'Chile'; case 'CN': return 'China'; case 'CO': return 'Columbia'; case 'ZR': return 'Democratic Republic of Congo'; case 'CG': return 'Republic of the Congo (Brazaville)'; case 'CR': return 'Costa Rica'; case 'CI': return 'Cote d\'Ivoire (Ivory Coast)'; case 'HR': return 'Croatia'; case 'CY': return 'Cyprus'; case 'CZ': return 'Czech Republic'; case 'DK': return 'Denmark'; case 'DJ': return 'Djibouti'; case 'DO': return 'Dominican Republic'; case 'EC': return 'Ecuador'; case 'EG': return 'Egypt'; case 'SV': return 'El Salvador'; case 'GQ': return 'Equatorial Guinea'; case 'ER': return 'Eritrea'; case 'EE': return 'Estonia'; case 'ET': return 'Ethiopia'; case 'DK': return 'Faroe Islands'; case 'FJ': return 'Fiji'; case 'FI': return 'Finland'; case 'FR': return 'France'; case 'GF': return 'French Guiana'; case 'PF': return 'French Polynesia (Tahitti)'; case 'GA': return 'Gabon'; case 'GE': return 'Georgia, Republic of'; case 'DE': return 'Germany'; case 'GH': return 'Ghana'; case 'GB': return 'Great Britain & Northern Ireland'; case 'GR': return 'Greece'; case 'GD': return 'Grenada'; case 'GP': return 'Guadeloupe'; case 'GT': return 'Guatemala'; case 'GN': return 'Guinea'; case 'GW': return 'Guinea-Bissau'; case 'GY': return 'Guyana'; case 'HT': return 'Haiti'; case 'HN': return 'Honduras'; case 'HK': return 'Hong Kong'; case 'HU': return 'Hungary'; case 'IS': return 'Iceland'; case 'IN': return 'India'; case 'ID': return 'Indonesia'; case 'IR': return 'Iran'; case 'IQ': return 'Iraq'; case 'IE': return 'Ireland (Eire)'; case 'IL': return 'Israel'; case 'IT': return 'Italy'; case 'JM': return 'Jamaica'; case 'JP': return 'Japan'; case 'JO': return 'Jordan'; case 'KG': return 'Kazakhstan'; case 'KE': return 'Kenya'; case 'KR': return 'South Korea, Republic of'; case 'KW': return 'Kuwait'; case 'KG': return 'Kyrgyzstan'; case 'LA': return 'Laos'; case 'LV': return 'Latvia'; case 'LS': return 'Lesotho'; case 'LR': return 'Liberia'; case 'LI': return 'Liechtenstein'; case 'LT': return 'Lithuania'; case 'LU': return 'Luxembourg'; case 'MO': return 'Macao'; case 'MK': return 'Macedonia, Republic of'; case 'MG': return 'Madagascar'; case 'PT': return 'Madeira Islands'; case 'MW': return 'Malawi'; case 'MY': return 'Malaysia'; case 'MV': return 'Maldives'; case 'ML': return 'Mali'; case 'MT': return 'Malta'; case 'MQ': return 'Martinique'; case 'MR': return 'Mauritania'; case 'MU': return 'Mauritius'; case 'MX': return 'Mexico'; case 'MD': return 'Moldova'; case 'MN': return 'Mongolia'; case 'MA': return 'Morocco'; case 'MZ': return 'Mozambique'; case 'NA': return 'Namibia'; case 'NR': return 'Nauru'; case 'NP': return 'Nepal'; case 'NL': return 'Netherlands (Holland)'; case 'AN': return 'Netherlands Antilles'; case 'NC': return 'New Caledonia'; case 'NZ': return 'New Zealand'; case 'NI': return 'Nicaragua'; case 'NE': return 'Niger'; case 'NG': return 'Nigeria'; case 'NO': return 'Norway'; case 'OM': return 'Oman'; case 'PK': return 'Pakistan'; case 'PA': return 'Panama'; case 'PG': return 'Papua New Guinea'; case 'PY': return 'Paraguay'; case 'PE': return 'Peru'; case 'PH': return 'Philippines'; case 'PL': return 'Poland'; case 'PT': return 'Portugal'; case 'QA': return 'Qatar'; case 'RO': return 'Romania'; case 'RU': return 'Russia (Russia Federation)'; case 'RW': return 'Rwanda'; case 'KN': return 'St. Christopher (St. Kitts) & Nevis'; case 'LC': return 'St. Lucia'; case 'VC': return 'St. Vincent & the Grenadines'; case 'SA': return 'Saudi Arabia'; case 'SN': return 'Senegal'; case 'YU': return 'Serbia Montenegro (Yugoslavia)'; case 'SC': return 'Seychelles'; case 'SL': return 'Sierra Leone'; case 'SG': return 'Singapore'; case 'SK': return 'Slovak Republic (Slovakia)'; case 'SI': return 'Slovenia'; case 'SB': return 'Solomon Islands'; case 'SO': return 'Somalia'; case 'ZA': return 'South Africa'; case 'ES': return 'Spain'; case 'LK': return 'Sri Lanka'; case 'SD': return 'Sudan'; case 'SZ': return 'Swaziland'; case 'SE': return 'Sweden'; case 'CH': return 'Switzerland'; case 'SY': return 'Syrian Arab Republic'; case 'TW': return 'Taiwan'; case 'TJ': return 'Tajikistan'; case 'TZ': return 'Tanzania'; case 'TH': return 'Thailand'; case 'TG': return 'Togo'; case 'TT': return 'Trinidad & Tobago'; case 'TN': return 'Tunisia'; case 'TR': return 'Turkey'; case 'TM': return 'Turkmenistan'; case 'UG': return 'Uganda'; case 'AE': return 'United Arab Emirates'; case 'UA': return 'Ukraine'; case 'US': return 'United States of America'; case 'UY': return 'Uruguay'; case 'VU': return 'Vanuatu'; case 'VE': return 'Venezuela'; case 'VN': return 'Vietnam'; case 'WS': return 'Western Samoa'; case 'YE': return 'Yemen'; default: return 'Not On File'; } } else { switch ($abbr) { case 'Albania': return 'AL'; case 'Algeria': return 'DZ'; case 'Andorra': return 'AD'; case 'Angola': return 'AO'; case 'Argentina': return 'AR'; case 'Armenia': return 'AM'; case 'Aruba': return 'AW'; case 'Australia': return 'AU'; case 'Austria': return 'AT'; case 'Azerbaijan': return 'AZ'; case 'Azores': return 'PT'; case 'Bahamas': return 'BS'; case 'Bahrain': return 'BH'; case 'Bangladesh': return 'BD'; case 'Barbados': return 'BB'; case 'Belarus': return 'BY'; case 'Belgium': return 'BE'; case 'Belize': return 'BZ'; case 'Benin': return 'BJ'; case 'Bermuda': return 'BM'; case 'Bhutan': return 'BT'; case 'Bolivia': return 'BO'; case 'Bosna-Herzegovina': return 'BA'; case 'Botswana': return 'BW'; case 'Brazil': return 'BR'; case 'Brunei Darussalam': return 'BN'; case 'Bulgaria': return 'BG'; case 'Burkina FASO': return 'BF'; case 'Burundi': return 'BI'; case 'Cambodia': return 'KH'; case 'Cameroon': return 'CM'; case 'Canada': return 'CA'; case 'Cape Verde': return 'CV'; case 'Cayman Islands': return 'KY'; case 'Cntl African Republic': return 'CF'; case 'Chad': return 'TS'; case 'Chile': return 'CL'; case 'China': return 'CN'; case 'Columbia': return 'CO'; case 'Democratic Republic of Congo': return 'ZR'; case 'Republic of the Congo (Brazaville)': return 'CG'; case 'Costa Rica': return 'CR'; case 'Cote d\'Ivoire (Ivory Coast)': return 'CI'; case 'Croatia': return 'HR'; case 'Cyprus': return 'CY'; case 'Czech Republic': return 'CZ'; case 'Denmark': return 'DK'; case 'Djibouti': return 'DJ'; case 'Dominican Republic': return 'DO'; case 'Ecuador': return 'EC'; case 'Egypt': return 'EG'; case 'El Salvador': return 'SV'; case 'Equatorial Guinea': return 'GQ'; case 'Eritrea': return 'ER'; case 'Estonia': return 'EE'; case 'Ethiopia': return 'ET'; case 'Faroe Islands': return 'DK'; case 'Fiji': return 'FJ'; case 'Finland': return 'FI'; case 'France': return 'FR'; case 'French Guiana': return 'GF'; case 'French Polynesia (Tahitti)': return 'PF'; case 'Gabon': return 'GA'; case 'Georgia, Republic of': return 'GE'; case 'Germany': return 'DE'; case 'Ghana': return 'GH'; case 'Great Britain & Northern Ireland': return 'GB'; case 'Greece': return 'GR'; case 'Grenada': return 'GD'; case 'Guadeloupe': return 'GP'; case 'Guatemala': return 'GT'; case 'Guinea': return 'GN'; case 'Guinea-Bissau': return 'GW'; case 'Guyana': return 'GY'; case 'Haiti': return 'HT'; case 'Honduras': return 'HN'; case 'Hong Kong': return 'HK'; case 'Hungary': return 'HU'; case 'Iceland': return 'IS'; case 'India': return 'IN'; case 'Indonesia': return 'ID'; case 'Iran': return 'IR'; case 'Iraq': return 'IQ'; case 'Ireland (Eire)': return 'IE'; case 'Israel': return 'IL'; case 'Italy': return 'IT'; case 'Jamaica': return 'JM'; case 'Japan': return 'JP'; case 'Jordan': return 'JO'; case 'Kazakhstan': return 'KG'; case 'Kenya': return 'KE'; case 'South Korea, Republic of': return 'KR'; case 'Kuwait': return 'KW'; case 'Kyrgyzstan': return 'KG'; case 'Laos': return 'LA'; case 'Latvia': return 'LV'; case 'Lesotho': return 'LS'; case 'Liberia': return 'LR'; case 'Liechtenstein': return 'LI'; case 'Lithuania': return 'LT'; case 'Luxembourg': return 'LU'; case 'Macao': return 'MO'; case 'Macedonia, Republic of': return 'MK'; case 'Madagascar': return 'MG'; case 'Madeira Islands': return 'PT'; case 'Malawi': return 'MW'; case 'Malaysia': return 'MY'; case 'Maldives': return 'MV'; case 'Mali': return 'ML'; case 'Malta': return 'MT'; case 'Martinique': return 'MQ'; case 'Mauritania': return 'MR'; case 'Mauritius': return 'MU'; case 'Mexico': return 'MX'; case 'Moldova': return 'MD'; case 'Mongolia': return 'MN'; case 'Morocco': return 'MA'; case 'Mozambique': return 'MZ'; case 'Namibia': return 'NA'; case 'Nauru': return 'NR'; case 'Nepal': return 'NP'; case 'Netherlands (Holland)': return 'NL'; case 'Netherlands Antilles': return 'AN'; case 'New Caledonia': return 'NC'; case 'New Zealand': return 'NZ'; case 'Nicaragua': return 'NI'; case 'Niger': return 'NE'; case 'Nigeria': return 'NG'; case 'Norway': return 'NO'; case 'Oman': return 'OM'; case 'Pakistan': return 'PK'; case 'Panama': return 'PA'; case 'Papua New Guinea': return 'PG'; case 'Paraguay': return 'PY'; case 'Peru': return 'PE'; case 'Philippines': return 'PH'; case 'Poland': return 'PL'; case 'Portugal': return 'PT'; case 'Qatar': return 'QA'; case 'Romania': return 'RO'; case 'Russia (Russia Federation)': return 'RU'; case 'Rwanda': return 'RW'; case 'St. Christopher (St. Kitts) & Nevis': return 'KN'; case 'St. Lucia': return 'LC'; case 'St. Vincent & the Grenadines': return 'VC'; case 'Saudi Arabia': return 'SA'; case 'Senegal': return 'SN'; case 'Serbia Montenegro (Yugoslavia)': return 'YU'; case 'Seychelles': return 'SC'; case 'Sierra Leone': return 'SL'; case 'Singapore': return 'SG'; case 'Slovak Republic (Slovakia)': return 'SK'; case 'Slovenia': return 'SI'; case 'Solomon Islands': return 'SB'; case 'Somalia': return 'SO'; case 'South Africa': return 'ZA'; case 'Spain': return 'ES'; case 'Sri Lanka': return 'LK'; case 'Sudan': return 'SD'; case 'Swaziland': return 'SZ'; case 'Sweden': return 'SE'; case 'Switzerland': return 'CH'; case 'Syrian Arab Republic': return 'SY'; case 'Taiwan': return 'TW'; case 'Tajikistan': return 'TJ'; case 'Tanzania': return 'TZ'; case 'Thailand': return 'TH'; case 'Togo': return 'TG'; case 'Trinidad & Tobago': return 'TT'; case 'Tunisia': return 'TN'; case 'Turkey': return 'TR'; case 'Turkmenistan': return 'TM'; case 'Uganda': return 'UG'; case 'United Arab Emirates': return 'AE'; case 'Ukraine': return 'UA'; case 'United States of America': return 'US'; case 'Uruguay': return 'UY'; case 'Vanuatu': return 'VU'; case 'Venezuela': return 'VE'; case 'Vietnam': return 'VN'; case 'Western Samoa': return 'WS'; case 'Yemen': return 'YE'; default: return 'XZ'; } } }
<?php function call($method, $url, $payload=false) { $privateKey = 'Your_Private_API_Key'; // Get the time $time = time(); // Use api key to generate security token using the REQUEST_URI and the time $token = hash_hmac('sha256', substr($url, strpos($url, '.com') + 4) . $time, $privateKey); $ch = curl_init(); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_FAILONERROR, true); // CURLOPT_SSL_VERIFYPEER set to false for testing only curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // CURLOPT_SSL_VERIFYHOST disabled for testing only. It should be set to 2 in production curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); curl_setopt($ch, CURLOPT_CAINFO, '/path/to/cacrt.pem'); //Set the authorization headers $headers = []; $hearers[] = 'Authorization: Basic ' . base64_encode("Your_Account_Number:Your_Public_API_Key"); $headers[] = "X-CI-Auth: $token"; $headers[] = "X-CI-Time: $time"; if ($payload) { // $payload should be an array of Resource Names and Values. $payloadJSON = json_encode($payload); curl_setopt($ch, CURLOPT_POSTFIELDS, $payloadJSON); $headers[] = 'Content-Type: application/json'; $headers[] = 'Content-Length: ' . strlen($payloadJSON); } curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $result = curl_exec($ch); if ($result === false) { return curl_error($ch); } return $result; }
<?php function buildURI($endPoint, $data) { /*** * $data['include'] = [ 'res1', 'res2', 'res3', ... ]; * Indicates specific resources to fetch from the endpoint * If $data['include'] is omitted all resources for entries matching the filter will be returned * * $data['exclude'] = [ 'res1', 'res2', 'res3', ... ]; * Indicates specific resources not to fetch from the endpoint * * If both 'include' and 'exclude' are present 'include' will be favored * * $data['filter'] = [ [ 'Resource'=>'resName', 'Filter'=>'filter', 'Value'=>'val' ] ]; * The above describes a simple 'AND' query. * The structure of an 'AND' statement simply addds elements to the primary array. EX: * $data['filter'] = [ * [ 'Resource'=>'resName', 'Filter'=>'filter', 'Value'=>'val' ], * [ 'Resource'=>'resName', 'Filter'=>'filter', 'Value'=>'val' ] * ]; * An 'OR' statement requires an array of simple 'AND' statements. Ex: * $data['filter'] = [ * [ * [ 'Resource'=>'resName', 'Filter'=>'filter', 'Value'=>'val' ], * [ 'Resource'=>'resName', 'Filter'=>'filter', 'Value'=>'val' ] * ], * [ 'Resource'=>'resName', 'Filter'=>'filter', 'Value'=>'val' ] * ] * The available filters: * cs: contain string (string contains value) * sw: start with (string starts with value) * ew: end with (string end with value) * eq: equal (string or number matches exactly) * lt: lower than (number is lower than value) * le: lower or equal (number is lower than or equal to value) * ge: greater or equal (number is higher than or equal to value) * gt: greater than (number is higher than value) * bt: between (number is between two comma separated values) * in: in (number is in comma separated list of values) * is: is null (field contains "NULL" value) * Negate any filter by prepending a 'n' character, ex: 'eq' becomes 'neq' * * If $data['filter'] is omitted requested resources will be returned for all entries * * With the "order" parameter you can sort by resource. * The default sort is in ascending order, but by specifying "desc" this can be reversed * $data['order'] = array(array('resource'=>'TicketNumber','dir'=>'desc')); * * You may sort on multiple fields by using multiple "order" parameters * $data['order'] = [ [ 'resource'=>'TicketNumber', 'dir'=>'desc' ], [ 'resource'=>'Contract' ] ]; * * * The "page" parameter holds the requested page. The default page size is 20, but can be adjusted (e.g. to 50) * $data['page'] = '1'; or $data['page'] = '1,50'; * * Requests that are not ordered cannot be paginated * * The "join" parameter is an indexed array of tables that use Foreign Key Constraints. Those constraints are described * in the above tables. Ex: * * $endpoint = 'invoices'; * $data['filter'][] = ['Resource'=>'BillTo', 'Filter'=>'eq', 'Value'=>0]; * $data['join'] = ['tickets']; * * This would return all invoices for client ID 0 and each of those invoices would have the resource 'Tickets' * which is an indexed array containing each of the tickets associated with that invoice. * * When using this function to update a resource (PUT): * $data should be an empty array * This functions return should have a string appended * This string should begin with a forward slash followed by the PRIMARY KEY value of the resource to be modified * An example using the call function defined above: * call('PUT', buildURI($endPoint, []) . '/' . $primaryKey, $payload); * $payload should be an array of key => value pairs to be updated at the $endPoint * See the tables above for the PRIMARY KEY resources to be used for these calls ***/ $baseURI = "https://rjdeliveryomaha.com/v2/records/"; $queryURI = $baseURI . $endPoint; if (empty($data)) { return $queryURI; } $query = []; // make sure the keys in $data are in the proper order $paramList = []; $params = $data; if (isset($data['exclude'])) { $paramList[] = 'exclude'; } if (isset($data['include'])) { $paramList[] = 'include'; } if (isset($data['filter'])) { $paramList[] = 'filter'; } if (isset($data['order'])) { $paramList[] = 'order'; } if (isset($data['page'])) { $paramList[] = 'page'; } if (isset($data['join'])) { $paramList[] = 'join'; } $data = array_merge(array_flip($paramList), $params); foreach ($data as $key => $value) { if ($key === 'exclude' && (!isset($data['include']) || empty($data['include']))) { $query['exclude'] = implode(',', $data['exclude']); } if ($key === 'include') { $query['include'] = implode(',', $data['include']); } elseif ($key === 'filter') { if (!isset($value[0][0])) { for ($i = 0; $i < count($value); $i++) { $this->query['filter'][] = implode(',', array_values($value[$i])); } } else { for ($i = 0; $i < count($value); $i++) { $filter_index = $i + 1; for ($j = 0; $j < count($value[$i]); $j++) { $this->query["filter{$filter_index}"][] = implode(',', array_values($value[$i][$j])); } } } } } if (!empty($query)) { $encodedQuery = http_build_query($query, null, '&', PHP_QUERY_RFC3986); // http://php.net/manual/en/function.http-build-query.php#111819 $encodedQuery = preg_replace('/%5B[0-9]+%5D/simU', '', $encodedQuery); } else { $encodedQuery = ''; } if (isset($data['order'])) { for ($i = 0; $i < count($data['order']); $i++) { if (strlen($encodedQuery) > 0) $encodedQuery .= '&'; $orderParam = preg_replace('/\s+/', '', $data['order'][$i]); $encodedQuery .= "order={$orderParam}"; } if (isset($data['page'])) { $paramTest = false; if (strpos(',', $data['page']) !== false) { $testVal = explode(',', $data['page']); $paramTest = count($testVal) === 2 && is_numeric($testVal[0]) && is_numeric($testVal[1]); } else { $paramTest = is_numeric($data['page']); } if ($paramTest === true) $encodedQuery .= "&page={$data['page']}"; } } if (isset($data['join'])) { for ($i = 0; $i < count($data['join']); $i++) { if (strlen($encodedQuery) > 0) $encodedQuery .= '&'; $joinParam = preg_replace('/\s+/', '', $this->queryParams['join'][$i]); $encodedQuery .= "join={$joinParam}"; } } return "{$queryURI}?{$encodedQuery}"; }
<?php function testResponse($method, $response) { // A simple test to make sure an appropriate response was received switch ($method) { // POST: Expects the id of the last created resource // PUT, DELETE: Expects the number of rows affected // Expects an array of ids or rows affected when creating, updating, or deleting with array // Receives the string 'null' on failure case "DELETE": case "PUT": case "POST": return (is_numeric($response) || substr($response, 0, 1) === '['); // Expects a json encoded object or array of objects case "GET": return (substr($response,0,1) === '{' || substr($response,0,1) === '['); default: return false; } }
<?php function responseError($response) { switch (filter_var($response, FILTER_SANITIZE_NUMBER_INT)) { case 400: echo 'Server Error: Invalid Request URI.'; break; case 401: echo 'Server Error: Invalid login credentials.'; break; case 403: echo 'Server Error: Login credentials not defined.'; break; case 404: echo 'Server Error: Failed to locate record.'; break; case 422: echo 'Server Error: Failed Data Validation. ' . substr($response, strpos($response,'422')+3); break; case 500: echo 'Server Error: Internal Error.'; break; case 503: echo "Server Error: Service temporarily unavailable.\n"; default: default: echo 'Server Error: ' . $response; break; } }
<?php /** usage: if ($_SERVER['REQUEST_METHOD'] !== "POST") return false; http_response_code(200); // PHP 5.4 or greater include_once("../ PATH / To / webhookHandler.class.php"); $webhookHandler = new webhookHandler(@file_get_contents("php://input")); $webhookHandler->processWebhook(); **/ class webhookHandler { // user config private $timezone = "UTC"; private $privateKey = "Your_Private_API_Key"; private $userID = "Your_Account_Number"; // properties in the webhook private $error; private $type; private $BillTo; private $DispatchedTo; private $TicketNumber; private $InvoiceNumber; private $ClientID; private $DriverID; private $DispatchID; private $RunNumber; private $ScheduleID; private $stored; private $received; // validation private $token; private $inputArray; // processing private $typeParts; private $transferKeys = [ "error", "type", "BillTo", "DispatchedTo", "TicketNumber", "InvoiceNumber", "ClientID", "DriverID", "DispatchID", "stored", "received" ]; // error handling private $content; private $fileWriteTry = 5; private $targetFile = "./logs/webhookHandler_error"; private $Data; function __construct($jsonString) { if ($jsonString === NULL || $jsonString === false) $this->exitWithoutLog(); $this->inputArray = json_decode($jsonString); if (json_last_error() !== JSON_ERROR_NONE || $this->inputArray === NULL) $this->exitWithoutLog(); $this->token = hash_hmac('sha256', $jsonString . $this->userID . $this->inputArray->timestamp, $this->privateKey); // hash_equals for php < 5.6.0 // https://php.net/manual/en/function.hash-equals.php#115664 // random_bytes alternatives for php < 7.0 // https://www.php.net/manual/en/function.random-bytes.php#118932 $headers = array_change_key_case(getallheaders(), CASE_UPPER); $auth = $headers['X-CI-AUTH'] ?? bin2hex(random_bytes(1e4)); if (!hash_equals($this->token, $auth)) { $this->content = "Failed Auth \n" . print_r($jsonString, true); $this->exitWithLog(); } if (!isset($this->inputArray["Data"]) || empty($this->inputArray["Data"])) { $this->content = "No webhook to process \n" . print_r($jsonString, true); } else { $this->Data = $this->inputArray["Data"]; } if (!date_default_timezone_set($this->timezone)) { $this->content = "Timezone error: Could not set timezone to \"{$this->timezone}\n----\n\n"; $this->writeLoop(); } } private function exitWithoutLog() { return false; } private function exitWithLog() { $this->content = ($this->content === NULL) ? date("dMY H:i:s") . "\nundefined error\n----\n\n" : date("dMY H:i:s") . "\n" . $this->content . "\n----\n\n"; return $this->writeLoop(); } private function writeLoop() { $i = 0; do { $test = $this->writeFile(); $i++; } while ($test !== strlen($this->content) && $i < $this->fileWriteTry); } private function writeFile() { /*** http://php.net/manual/en/function.fwrite.php#81269 ***/ $fp = fopen( $this->targetFile, "ab" ); /*** write the new file content ***/ $bytes_to_write = strlen($this->content); $bytes_written = 0; while ($bytes_written < $bytes_to_write) { if ($bytes_written == 0) { $rv = fwrite($fp, $this->content); } else { $rv = fwrite($fp, substr($this->content, $bytes_written)); } if ($rv === false || $rv == 0) { return($bytes_written == 0 ? false : $bytes_written); } $bytes_written += $rv; } return $bytes_written; } public function processWebhook() { for ($i = 0; $i < count($this->Data); $i++) { // TODO // Implement deduplication for batch webhooks if desired foreach ($this->Data[$i] as $key => $value) { foreach ($this as $k => $v) { if (in_array($key, $this->transferKeys) && $key === $k) { $this->$k = $value; } } } $this->executeWebhook(); } } private function executeWebhook() { if (empty($this->Data)) $this->exitWithoutLog(); if ($this->error !== NULL) { $this->content = $this->error; $this->exitWithLog(); } else { $this->typeParts = explode(".", $this->type); $method = $this->typeParts[0]; if (method_exists($this, $method)) return $this->$method(); $this->content = "Invalid primary hook component: {$this->type}"; $this->exitWithLog(); } } // ticket methods private function ticket() { $method = $this->typeParts[2] . "Ticket"; switch($this->typeParts[1]) { case "contract": // TODO // differentiate between contract and on call tickets case "oncall": if (method_exists($this, $method)) return $this->$method(); $this->content = "Invalid tertiary hook component: {$this->type}"; $this->exitWithLog(); break; default: $this->content = "Invalid secondary hook component: {$this->type}"; $this->exitWithLog(); } } private function receiveTicket() { $this->content = "Ticket {$this->TicketNumber} received"; $this->content .= "\n" . print_r($this->Data, true); $this->exitWithLog(); } private function dispatchTicket() { $this->content = "Ticket {$this->TicketNumber} dispatched to driver {$this->DispatchedTo}"; $this->content .= "\n" . print_r($this->Data, true); $this->exitWithLog(); } private function pickupTicket() { $this->content = "Ticket {$this->TicketNumber} picked up"; $this->content .= "\n" . print_r($this->Data, true); $this->exitWithLog(); } private function deliverTicket() { $this->content = "Ticket {$this->TicketNumber} delivered"; $this->content .= "\n" . print_r($this->Data, true); $this->exitWithLog(); } private function returnTicket() { $this->content = "Ticket {$this->TicketNumber} returned"; $this->content .= "\n" . print_r($this->Data, true); $this->exitWithLog(); } private function updateTicket() { if ( count(get_object_vars($this->received)) === 2 && property_exists($this->received, "InvoiceNumber") & & property_exists($this->received, "invoice_id") ) { // uncomment the following to prevent sending push notifications // for each ticket when invoices are created via the API // return false; } if ( count(get_object_vars($this->received)) === 2 && ((property_exists($this->received, 'pLat') && property_exists($this->received, 'pLng')) || (property_exists($this->received, 'dLat') && property_exists($this->received, 'dLng')) || (property_exists($this->received, 'd2Lat') && property_exists($this->received, 'd2Lng'))) ) { // uncomment the following to prevent sending push notifications when coordinates are updated for only one step // return false; } $this->content = "Ticket {$this->TicketNumber} updated"; $this->content .= "\n" . print_r($this->Data, true); $this->exitWithLog(); } private function transferTicket() { $this->content = "Ticket {$this->TicketNumber} transferred"; $this->content .= "\n" . print_r($this->Data, true); $this->exitWithLog(); } private function deleteTicket() { $this->content = "Ticket {$this->TicketNumber} deleted"; $this->content .= "\n" . print_r($this->Data, true); $this->exitWithLog(); } // invoice methods private function invoice() { $method = $this->typeParts[1] . "Invoice"; if(method_exists($this, $method)) return $this->$method(); $this->content = "Invalid secondary hook component: {$this->type}"; $this->exitWithLog(); } private function createInvoice() { $this->content = "Invoice {$this->InvoiceNumber} created"; $this->content .= "\n" . print_r($this->Data, true); $this->exitWithLog(); } private function updateInvoice() { $this->content = "Invoice {$this->InvoiceNumber} updated"; $this->content .= "\n" . print_r($this->Data, true); $this->exitWithLog(); } private function deleteInvoice() { $this->content = "Invoice {$this->InvoiceNumber} deleted"; $this->content .= "\n" . print_r($this->Data, true); $this->exitWithLog(); } // client methods private function client() { $method = $this->typeParts[2] . "Client"; if(method_exists($this, $method)) return $this->$method(); $this->content = "Invalid secondary hook component: {$this->type}"; $this->exitWithLog(); } private function createClient() { $this->content = "{$this->typeParts[1]} Client {$this->ClientID} {$this->typeParts[2]}d"; $this->content .= "\n" . print_r($this->Data, true); $this->exitWithLog(); } private function updateClient() { return $this->createClient(); } private function deleteClient() { return $this->createClient(); } private function t_client() { return $this->client(); } private function o_client() { return $this->client(); } // driver / dispatcher methods private function driver() { $method = $this->typeParts[1] . "Driver"; if(method_exists($this, $method)) return $this->$method(); $this->content = "Invalid secondary hook component: {$this->type}"; $this->exitWithLog(); } private function createDriver() { $this->content = "{$this->type[0]} {$this->DriverID} {$this->typeParts[1]}d"; $this->content .= "\n" . print_r($this->Data, true); $this->exitWithLog(); } private function updateDriver() { return $this->createDriver(); } private function deleteDriver() { return $this->createDriver(); } private function dispatcher() { $method = $this->typeParts[1] . "Dispatcher"; if(method_exists($this, $method)) return $this->$method(); $this->content = "Invalid secondary hook component: {$this->type}"; $this->exitWithLog(); } private function createDispatcher() { $this->content = "{$this->type[0]} {$this->DispatchID} {$this->typeParts[1]}d"; $this->content .= "\n" . print_r($this->Data, true); $this->exitWithLog(); } private function updateDispatcher() { return $this->createDispatcher(); } private function deleteDispatcher() { return $this->createDispatcher(); } private function contract_run() { $method = $this->typeParts[1] . "Run"; if(method_exists($this, $method)) return $this->$method(); $this->content = "Invalid secondary hook component: {$this->type}"; $this->exitWithLog(); } private function createRun() { $this->content = "{$this->type[0]} {$this->RunNumber} {$this->typeParts[1]}d"; $this->content .= "\n" . print_r($this->Data, true); $this->exitWithLog(); } private function updateRun() { return $this->createRun(); } private function deleteRun() { return $this->createRun(); } private function schedule_override() { $method = $this->typeParts[1] . "Schedule"; if(method_exists($this, $method)) return $this->$method(); $this->content = "Invalid secondary hook component: {$this->type}"; $this->exitWithLog(); } private function createSchedule() { $this->content = "{$this->type[0]} {$this->ScheduleID} {$this->typeParts[1]}d"; $this->content .= "\n" . print_r($this->Data, true); $this->exitWithLog(); } private function updateSchedule() { return $this->createSchedule(); } private function deleteSchedule() { return $this->createSchedule(); } }
<?php /** this should be run as a cron job the day following the end of a monthly billing cycle. In terms of timing this is very simplistic this class does not account for leap years and assumes that invoices will not be generated after the 28th of any month usage: include_once '../ PATH / TO /invoiceCron.class.php'; $invoiceCron = new invoiceCron(); $invoiceCron->createInvoices(); **/ class invoiceCron { private $username = 'Your_Account_Number'; private $publicKey = 'Your_Public_API_Key'; private $privateKey = 'Your_Private_API_Key'; private $timezoneString = 'UTC'; private $logSuccess = false; // array of (int)ClientID that should not be processed on this schedule private $ignoreClients = []; private $ignoreNonRepeat = []; // invoice variables private $defaultTerms = 1; private $discountRate = 0; private $discountWindow = 0; private $termLength = 0; private $startDate; private $endDate; private $dateIssued; private $tickets; private $clientList; private $newInvoices; private $DateIssued; private $Over90InvoiceList; // update variables private $ticketUpdateKeys; private $ticketUpdateValues; // query variables private $method; private $primaryKey; private $baseURI; private $ch; private $timeVal; private $token; private $queryURI; private $headers; private $jsonData; private $message; private $query; private $result; private $payload = false; private $data; private $endPoint; // error catching private $timezone; private $today; private $content; private $fileWriteTry = 5; private $targetFile = './invoice_error'; public function __construct() { // Extend timeout for large queries set_time_limit(3600); $this->clients = $this->t_clients = $this->clientList = $this->headers = $this->query = $this->data = $this->ticketUpdateKeys = $this->ticketUpdateValues = []; // set timezone try { $this->timezone = new dateTimezone($this->timezoneString); } catch (Exception $e) { $this->content = date('Y-m-d H:i:s') . "\nTimezone Error: {$e->getMessage()}\n\n"; $this->writeLoop(); exit; } // set today's date try { $this->today = new dateTime('NOW', $this->timezone); } catch(Exception $e) { $this->content = date('Y-m-d H:i:s') . "\nDate Error: {$e->getMessage()}\n\n"; $this->writeLoop(); exit; } // set the invoice start date $tempDate = clone $this->today; $tempDate->modify('- 1 month'); $this->startDate = $tempDate->format('Y-m-d'); // set the invoice end date $tempDate = clone $this->today; $tempDate->modify('- 1 day'); $this->endDate = $tempDate->format('Y-m-d'); // set DateIssued for all invoices $this->DateIssued = $this->today->format('Y-m-d'); // define the base uri $this->baseURI = 'https://rjdeliveryomaha/v2/records/'; } public function createInvoices() { // fetch all tickets that have not been billed this cycle $this->fetchTickets(); // fetch the terms for clients with tickets to be invoiced $this->fetchTerms(); // fetch most recent invoice numbers and forwarded balances $this->fetchLastInvoice(); // process new invoices $this->processInvoices(); // submit invoices to the API $this->submitInvoices(); // submit ticket updates to API $this->submitTickets(); // log success if ($this->logSuccess === true) { $this->content = $this->today->format("d M Y H:i:s.u") . "\n" . count($this->newInvoices) . " Invoices Created\n\n"; $this->writeLoop(); } exit; } // start utility functions private function clearParameters() { $this->headers = $this->query = $this->data = []; $this->jsonData = ''; $this->payload = false; } private function test_int($val) { return (int)round($this->test_float($val), 0, PHP_ROUND_HALF_EVEN); } private function test_float($val) { return (float)filter_var($val, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION); } // Search function for returning part of a string private function after($needle, $haystack) { if (!is_bool(strpos($haystack, $needle))) return substr($haystack, strpos($haystack,$needle)+strlen($needle)); } private function before($needle, $haystack) { return substr($haystack, 0, strpos($haystack, $needle)); } private function between($needle, $needle2, $haystack) { return $this->before($needle2, $this->after($needle, $haystack)); } private function writeLoop() { $i = 0; do { $test = $this->writeFile(); $i++; } while ($test !== strlen($this->content) && $i < $this->fileWriteTry); } private function writeFile() { /*** http://php.net/manual/en/function.fwrite.php#81269 ***/ /*** open the file for writing and truncate it to zero length ***/ $fp = fopen( $this->targetFile, 'ab' ); /*** write the new file content ***/ $bytes_to_write = strlen($this->content); $bytes_written = 0; while ($bytes_written < $bytes_to_write) { if ($bytes_written == 0) { $rv = fwrite($fp, $this->content); } else { $rv = fwrite($fp, substr($this->content, $bytes_written)); } if ($rv === false || $rv == 0) { return($bytes_written == 0 ? false : $bytes_written); } $bytes_written += $rv; } return $bytes_written; } private function testResponse() { // A simple test to make sure an appropriate response was received switch (strtoupper($this->method)) { // POST: Expects the id of the last created resource // PUT, DELETE: Expects the number of rows affected // Expects an array of ids or rows affected when creating, updating, or deleting with array // Receives the string 'null' on failure case 'DELETE': case 'PUT': case 'POST': return (is_numeric($this->result) || substr($this->result, 0, 1) === '['); // Expects a json encoded object or array of objects case 'GET': return (substr($this->result,0,1) === '{' || substr($this->result,0,1) === '['); default: return false; } } private function responseError() { switch ($this->test_int($this->result)) { case 400: $this->content = "Server Error: Invalid Request URI.\n"; break; case 401: $this->content = "Server Error: Invalid login credentials.\n"; break; case 403: $this->content = "Server Error: Login credentials not defined.\n"; break; case 404: $this->content = "Server Error: Failed to locate record.\n"; break; case 422: $this->content = "Server Error: Failed Data Validation. {$this->after('422', $this->result)}\n"; break; case 500: $this->content = "Server Error: Internal Error.\n"; break; case 503: $this->content = "Server Error: Service temporarily unavailable.\n"; default: $this->content = "Server Error: {$this->result}.\n"; break; } $this->writeLoop(); exit; } private function buildURI() { if (!is_array($this->data) || empty($this->data)) { $this->queryURI = $this->baseURI . $this->endPoint; } else { foreach ($this->data as $key => $value) { if ($key === 'resources') { $this->query['include'] = implode(",",$this->data['resources']); } elseif ($key === 'filter') { if (!isset($value[0][0])) { for ($i = 0; $i < count($value); $i++) { $this->query['filter'][] = implode(',', array_values($value[$i])); } } else { for ($i = 0; $i < count($value); $i++) { $filter_index = $i + 1; for ($j = 0; $j < count($value[$i]); $j++) { $this->query["filter{$filter_index}"][] = implode(',', array_values($value[$i][$j])); } } } } else { $this->query[$key] = implode(',',$value); } } $temp2 = http_build_query($this->query,NULL,'&',PHP_QUERY_RFC3986); // http://php.net/manual/en/function.http-build-query.php#111819 $temp2 = preg_replace('/%5B[0-9]+%5D/simU', '', $temp2); $this->queryURI = "{$this->baseURI}{$this->endPoint}?{$temp2}"; } } private function call() { if ($this->primaryKey != NULL) { $this->queryURI .= "/{$this->primaryKey}"; } // Use api key to generate security token $this->timeVal = time(); // Generate the security key using the REQUEST_URI $this->token = hash_hmac( 'sha256', substr($this->queryURI, strpos($this->queryURI, '.com') + 4) . $this->timeVal, $this->privateKey ); $this->ch = curl_init(); curl_setopt($this->ch, CURLOPT_CUSTOMREQUEST, strtoupper($this->method)); curl_setopt($this->ch, CURLOPT_URL, $this->queryURI); curl_setopt($this->ch, CURLOPT_FAILONERROR, TRUE); // CURLOPT_SSL_VERIFYPEER set to false for testing only curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER, TRUE); // CURLOPT_SSL_VERIFYHOST set to 0 for testing only curl_setopt($this->ch, CURLOPT_SSL_VERIFYHOST, 2); curl_setopt($this->ch, CURLOPT_CAINFO, __DIR__ . DIRECTORY_SEPARATOR . 'cacert.pem'); // Set the authorization headers $this->headers[] = "Authorization: Basic " . base64_encode("{$this->username}:{$this->publicKey}"); $this->headers[] = "X-CI-Auth: {$this->token}"; $this->headers[] = "X-CI-Time: {$this->timeVal}"; if ($this->payload !== false) { $this->jsonData = json_encode($this->payload); curl_setopt($this->ch, CURLOPT_POSTFIELDS, $this->jsonData); $this->headers[] = 'Content-Type: application/json'; $this->headers[] = 'Content-Length: ' . strlen($this->jsonData); } curl_setopt($this->ch, CURLOPT_HTTPHEADER, $this->headers); curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, TRUE); $this->result = curl_exec($this->ch); if ($this->result === false) { $this->result = curl_error($this->ch); } curl_close($this->ch); if (!$this->testResponse()) { $this->responseError(); } } // end utility functions private function fetchTickets() { $this->clearParameters(); $this->endPoint = 'tickets'; $this->method = 'GET'; $this->data['resources'] = [ 'ticket_index', 'TicketPrice', 'Charge', 'RepeatClient', 'BillTo' ]; // Create filter for repeat clients $repeatFilter = [ [ 'Resource'=>'ReceivedDate', 'Filter'=>'bt', 'Value'=>"{$this->startDate} 00:00:00, {$this->endDate} 11:59:59" ], [ 'Resource'=>'InvoiceNumber', 'Filter'=>'eq', 'Value'=>'-' ], [ 'Resource'=>'RepeatClient', 'Filter'=>'eq', 'Value'=>1 ] ]; if (!empty($this->ignoreClients)) $repeatFilter[] = [ 'Resource'=>'ClientID', 'Filter'=>'nin', 'Value'=>implode(',', $this->ignoreClients) ]; // Create filter for non-repeat clients $nonrepeatFilter = [ [ 'Resource'=>'ReceivedDate', 'Filter'=>'bt', 'Value'=>"{$this->startDate} 00:00:00, {$this->endDate} 11:59:59" ], [ 'Resource'=>'InvoiceNumber', 'Filter'=>'eq', 'Value'=>'-' ], [ 'Resource'=>'RepeatClient', 'Filter'=>'eq', 'Value'=>0 ] ]; if (!empty($this->ignoreNonRepeat)) $repeatFilter[] = [ 'Resource'=>'ClientID', 'Filter'=>'nin', 'Value'=>implode(',', $this->ignoreNonRepeat) ]; $this->data['filter'] = [ $repeatFilter, $nonrepeatFilter ]; $this->buildURI(); $this->call(); $temp = json_decode($this->result); if (empty($temp->records)) { $this->content = "{$this->today->format('d M Y H:i:s.u')}\nNo Tickets To Process\n\n" . print_r($this->data, true) . "\n" . print_r($this->result, true) . "\n----\n"; $this->writeLoop(); exit; } for ($i = 0; $i < count($temp->records); $i++) { $key = ($temp->records[$i]['RepeatClient'] == 1) ? $temp->records[$i]['BillTo'] : "t{$temp->records[$i]['BillTo']}"; if (!array_key_exists($key, $this->clientList)) $this->clientList[$key] = [ 'tickets'=>[], 'lastInvoice'=>[], 'openInvoices'=>[], 'terms'=> [ 'InvoiceTerms'=>1, 'DiscountRate'=>0, 'DiscountWindow'=>0, 'TermLength'=>0 ] ]; $this->clientList[$key]['tickets'][] = $temp[$i]; } } private function fetchTerms() { $this->clearParameters(); $this->endPoint = 'clients'; $this->method = 'GET'; $this->data['resources'] = [ 'ClientID', 'RepeatClient', 'ClientTerms', 'DiscountRate', 'DiscountWindow', 'TermLength' ]; // Split repeat and non-repeat clientIDs into separate arrays $repeats = $nonrepeats = $repeatFilter = $nonrepeatFilter = []; foreach($this->clientList as $key => $value) { if (strpos($key,'t') === false) { $repeats[] = $key; } else { $nonrepeats[] = substr($key, 1); } } if (!empty($repeats)) { $repeatFilter = [ ['Resource'=>'ClientID', 'Filter'=>'in', 'Value'=>implode(',', $repeats)], ['Resource'=>'RepeatClient', 'Filter'=>'eq', 'Value'=>1] ]; } if (!empty($nonrepeats)) { $nonrepeatFilter = [ ['Resource'=>'ClientID', 'Filter'=>'in', 'Value'=>implode(',', $nonrepeats)], ['Resource'=>'RepeatClient', 'Filter'=>'eq', 'Value'=>0] ]; } if (!empty($repeatFilter) && !empty($nonrepeatFilter)) { $this->data['filter'] = [ $repeatFilter, $nonrepeatFilter ]; } else { $this->data['filter'] = (empty($nonrepeatFilter)) ? $repeatFilter : $nonrepeatFilter; } $this->buildURI(); $this->call(); for ($i = 0; $i < count($this->result); $i++) { $key = $this->result[$i]['RepeatClient'] == 1 ? $this->result[$i]['ClientID'] : "t{$this->result[$i]['ClientID']}"; if ($this->result[$i]['ClientTerms'] == 0) { $this->clientList[$key]['terms']['InvoiceTerms'] = $this->defaultTerms; $this->clientList[$key]['terms']['DiscountRate'] = $this->discountRate; $this->clientList[$key]['terms']['DiscountWindow'] = $this->discountWindow; $this->clientList[$key]['terms']['TermLength'] = $this->termLength; } else { $this->clientList[$key]['terms']['InvoiceTerms'] = $this->result[$i]['ClientTerms']; $this->clientList[$key]['terms']['DiscountRate'] = $this->result[$i]['DiscountRate']; $this->clientList[$key]['terms']['DiscountWindow'] = $this->result[$i]['DiscountWindow']; $this->clientList[$key]['terms']['TermLength'] = $this->result[$i]['TermLength']; } } } private function fetchLastInvoice() { $this->clearParameters(); $this->endPoint = 'invoices'; $this->method = 'GET'; $this->data['resources'] = [ 'InvoiceNumber', 'RepeatClient', 'BalanceForwarded', 'InvoiceSubTotal', 'DateIssued', 'Closed', 'Deleted' ]; $repeats = $nonrepeats = $repeatFilter = $nonrepeatFilter = []; foreach($this->clientList as $key => $value) { if (strpos($key,'t') === false) { $repeats[] = $key; } else { $nonrepeats[] = substr($key, 1); } } if (!empty($repeats)) { $repeatFilter = [ [ 'Resource'=>'ClientID', 'Filter'=>'in', 'Value'=>implode(',', $repeats) ], [ 'Resource'=>'RepeatClient', 'Filter'=>'eq', 'Value'=>1 ] ]; } if (!empty($nonrepeats)) { $nonrepeatFilter = [ [ 'Resource'=>'ClientID', 'Filter'=>'in', 'Value'=>implode(',', $nonrepeats) ], [ 'Resource'=>'RepeatClient', 'Filter'=>'eq', 'Value'=>0 ] ]; } if (!empty($repeatFilter) && !empty($nonrepeatFilter)) { $this->data['filter'] = [ $repeatFilter, $nonrepeatFilter ]; } else { $this->data['filter'] = (empty($nonrepeatFilter)) ? $repeatFilter : $nonrepeatFilter; } $this->buildURI(); $this->call(); $temp = json_decode($this->result); for ($i = 0; $i < count($temp->records); $i++) { $tempID = substr($temp->records[$i]['InvoiceNumber'], strpos($temp->records[$i]['InvoiceNumber'],'-') + 1); if ($temp->records[$i]['Closed'] === 0 && $temp->records[$i]['Deleted'] === 0) { $this->clientList[$tempID]['openInvoices'][] = $temp->records[$i]; } if (empty($this->clientList[$tempID]['lastInvoice'])) { $this->clientList[$tempID]['lastInvoice'] = $temp->records[$i]; } else { $this->clientList[$tempID]['lastInvoice'] = ($this->clientList[$tempID]['lastInvoice']['DateIssued'] > $temp->records[$i]['DateIssued']) ? $this->clientList[$tempID]['lastInvoice'] : $temp->records[$i]; } } } private function processInvoices() { foreach ($this->clientList as $key => $value) { if (empty($value['tickets'])) continue; // create invoice object for submission $tempInvoice = new stdClass(); $tempInvoice->ClientID = $this->test_int($key); $tempInvoice->RepeatClient = (substr($key,0,1) === 't') ? 0 : 1; $tempInvoice->StartDate = $this->startDate; $tempInvoice->EndDate = $this->endDate; $tempInvoice->DateIssued = $this->DateIssued; // create new InvoiceNumber $invoicePointer = mt_rand(1000, 1100); if (!empty($value['lastInvoice'])) { $invoicePointer = (int)$this->between('X', '-', $value['lastInvoice']['InvoiceNumber']) + 1; } $tempInvoice->InvoiceNumber = "{$this->today->format('y')}EX{$invoicePointer}-{$key}"; // solve the invoice subtotal and prep tickets to be update with new invoice number $tempInvoice->InvoiceTotal = $tempInvoice->InvoiceSubTotal = $this->getTotal($value['tickets'], $tempInvoice->InvoiceNumber); // solve the amount due for the current invoice $tempInvoice->AmountDue = (!empty($value['lastInvoice'])) ? $tempInvoice->InvoiceSubTotal - $value['lastInvoice']['BalanceForwarded'] : $tempInvoice->InvoiceSubTotal; // solve the total due for all open invoices if (array_key_exists('openInvoices', $value) && !empty($value['openInvoices'])) { // Past due invoices need to be sortted by age and added to InvoiceTotal $date1 = $this->today; for ($i = 0; $i < count($value['openInvoices']); $i++) { $flag30 = $flag60 = $flag90 = $flagover90 = false; try { $date2 = new dateTime($value['openInvoices'][$i]['DateIssued'], $this->timezone); } catch(Exception $e) { $this->content = "{$today->format("d M Y H:i:s.u")}\nDate Error Line " . __line__ . ": {$e->getMessage()}\n\n"; $this->writeLoop(); exit; } $diff = $date2->diff($date1); if ($value['openInvoices'][$i]['InvoiceTerms'] == 3) { switch ($diff->m) { case 0: $flag30 = $date1->format('n') != $date2->format('n') && $date1->format('j') >= $value['openInvoices'][$i]['TermLength']; break; case 1: $flag30 = true; break; case 2: $flag60 = true; break; case 3: $flag90 = true; break; default: $flagover90 = true; } } else { if ( ($value['openInvoices'][$i]['InvoiceTerms'] == 1 && $diff->days < 60) || ($value['openInvoices'][$i]['InvoiceTerms'] > 1 && ($value['openInvoices'][$i]['TermLength'] <= $diff->days && $diff->days < $value['openInvoices'][$i]['TermLength'] * 2)) ) { $flag30 = true; } elseif ( ($value['openInvoices'][$i]['InvoiceTerms'] == 1 && (60 <= $diff->days && $diff->days < 90)) || ($value['openInvoices'][$i]['TermLength'] * 2 <= $diff->days && $diff->days < $value['openInvoices'][$i]['TermLength'] * 3) ) { $flag60 = true; } elseif ( ($value['openInvoices'][$i]['InvoiceTerms'] == 1 && (90 <= $diff->days && $diff->days < 180)) || ($value['openInvoices'][$i]['TermLength'] * 3 <= $diff->days && $diff->days < $value['openInvoices'][$i]['TermLength'] * 4) ) { $flag90 = true; } elseif ( ($value['openInvoices'][$i]['InvoiceTerms'] == 1 && ($diff->days >= 180)) || $diff->days >= $value['openInvoices'][$i]['TermLength'] * 4 ) { $flagover90 = true; } } if ($flag30 === true) { $tempInvoice->InvoiceTotal += $value['openInvoices'][$i]['InvoiceSubTotal']; if (property_exists($tempInvoice, 'Late30Invoice')) { $tempInvoice->Late30Invoice .= (strpos($tempInvoice->Late30Invoice, '+') === false) ? '+' : ''; } else { $tempInvoice->Late30Invoice = $value['openInvoices'][$i]['InvoiceNumber']; } if (property_exists($tempInvoice, 'Late30Value')) { $tempInvoice->Late30Value += $value['openInvoices'][$i]['InvoiceSubTotal']; } else { $tempInvoice->Late30Value = $value['openInvoices'][$i]['InvoiceSubTotal']; } } elseif ($flag60 === true) { $tempInvoice->InvoiceTotal += $value['openInvoices'][$i]['InvoiceSubTotal']; if (property_exists($tempInvoice, 'Late60Invoice')) { $tempInvoice->Late60Invoice .= (strpos($tempInvoice->Late60Invoice, '+') === false) ? '+' : ''; } else { $tempInvoice->Late60Invoice = $value['openInvoices'][$i]['InvoiceNumber']; } if (property_exists($tempInvoice, 'Late60Value')) { $tempInvoice->Late60Value += $value['openInvoices'][$i]['InvoiceSubTotal']; } else { $tempInvoice->Late60Value = $value['openInvoices'][$i]['InvoiceSubTotal']; } } elseif ($flag90 === true) { $tempInvoice->InvoiceTotal += $value['openInvoices'][$i]['InvoiceSubTotal']; if (property_exists($tempInvoice, 'Late90Invoice')) { $tempInvoice->Late90Invoice .= (strpos($tempInvoice->Late90Invoice, '+') === false) ? '+' : ''; } else { $tempInvoice->Late90Invoice = $value['openInvoices'][$i]['InvoiceNumber']; } if (property_exists($tempInvoice, 'Late90Value')) { $tempInvoice->Late90Value += $value['openInvoices'][$i]['InvoiceSubTotal']; } else { $tempInvoice->Late90Value = $value['openInvoices'][$i]['InvoiceSubTotal']; } } elseif ($flagover90 === true) { $tempInvoice->InvoiceTotal += $value['openInvoices'][$i]['InvoiceSubTotal']; $this->Over90InvoiceList[] = $value['openInvoices'][$i]['InvoiceNumber']; if (property_exists($tempInvoice, 'Over90Value')) { $tempInvoice->Over90Value += $value['openInvoices'][$i]['InvoiceSubTotal']; } else { $tempInvoice->Over90Value = $value['openInvoices'][$i]['InvoiceSubTotal']; } } // Fix the over90Invioce to display a maximum of 4 invoice numbers or 3 invoice numbers // and how many are not displayed, but only if there is at least one if (!empty($this->Over90InvoiceList)) { if (count($this->Over90InvoiceList) > 4) { $j = count($this->Over90InvoiceList) - 3; $appendment = ", + $j more"; $tempInvoice->Over90Invoice = implode(', ', array_slice($this->Over90InvoiceList, 0, 3)); $tempInvoice->Over90Invoice .= $appendment; } else { $tempInvoice->Over90Invoice = implode(', ',$this->Over90InvoiceList); } } } // Clear this list for the next iteration $this->Over90InvoiceList = []; } // add invoice object to array for submission $this->newInvoices[] = $tempInvoice; } } private function getTotal($ticketArray, $invoiceNumber) { if (!is_array($ticketArray)) return 0; $total = 0; for ($i = 0; $i < count($ticketArray); $i++) { $total += ($ticketArray[$i]['Charge'] !== 0) ? $ticketArray[$i]['TicketPrice'] : 0; $this->ticketUpdateKeys[] = $ticketArray[$i]['ticket_index']; $temp = new stdClass(); $temp->InvoiceNumber = $invoiceNumber; $this->ticketUpdateValues[] = $temp; } return $total; } private function submitInvoices() { $this->clearParameters(); $this->payload = $this->newInvoices; $this->endPoint = 'invoices'; $this->method = 'POST'; $this->buildURI(); $this->call(); foreach ($this->result as $i => $index) { for($j = 0; $j < count($this->ticketUpdateValues); $j++) { if ($this->ticketUpdateValues[$j]->InvoiceNumber === $this->newInvoices[$i]->InvoiceNumber) { $this->ticketUpdateValues[$j]->invoice_id = $index; } } } } private function submitTickets() { $this->clearParameters(); $this->primaryKey = implode(',', $this->ticketUpdateKeys); $this->payload = $this->ticketUpdateValues; $this->endPoint = 'tickets'; $this->method = 'PUT'; $this->buildURI(); $this->call(); } }