* @author Elisamuel Resto * @license New BSD License * @package LinkPoint * @category PaymentModules */ namespace LinkPoint; // Exceptions require_once 'Exception.php'; /** * An API interface for the FirstDat (formerly LinkPoint) payments API * * @todo Implement calcshipping method, calctax method, periodic entity (recurring billing). */ class LinkPoint { // Transaction Types const TYPE_SALE = 'sale'; const TYPE_AUTH = 'preauth'; const TYPE_CAPTURE = 'postauth'; const TYPE_VOID = 'void'; const TYPE_CREDIT = 'credit'; // Transaction Origin const ORIGIN_RETAIL = 'RETAIL'; const ORIGIN_TELEPHONE = 'TELEPHONE'; const ORIGIN_MAILORDER = 'MOTO'; const ORIGIN_ECI = 'ECI'; // Terminal Type const TERMINAL_STANDALONE = 'STANDALONE'; const TERMINAL_POINTOFSALE = 'POS'; const TERMINAL_UNATTENDED = 'UNATTENDED'; const TERMINAL_UNSPECIFIED = 'UNSPECIFIED'; const MODE_LIVE = 'live'; const MODE_TEST = 'test'; /** * Merchant store name or store number. * @var string */ protected $_storeName; /** * Path and filename for the API digital certificate. * @var string */ protected $_keyFile; /** * Hostname of the payment gateway server. * @var string */ protected $_host = 'secure.linkpt.net'; /** * Port for the gateway server. * @var int */ protected $_port = 1129; /** * Timeout in seconds, set to 0 for infinite * @var int */ protected $timeout = 15; /** * The cURL handle * @var resource */ private $_ch = null; /** * Boolean if the system should require AVS checks * @var boolean */ protected $_avsRequired = false; /** * The order ID. Must be unique for pre-authorisation, * or must be valid from a prior authorisation for VOID, * CREDIT, and CAPTURE. * @var string */ protected $_orderId; /** * Invoice number. If NULL, set to a normalised version * of the order ID upon processing. * @var string */ protected $_invoiceId = null; /** * Transaction date timestamp from result or previous transaction */ protected $_tdate = false; /** * Tax exemption status. * @var boolean */ protected $_taxExempt = false; /** * Customer-supplied purchase order number. * @var string */ protected $_purchaseOrder = null; /** * Purchase subtotal. Optional. * @var float */ protected $_subtotal = null; /** * Tax amount. * @var float */ protected $_tax = 0.00; /** * VAT amount. Optional. * @var float */ protected $_vat = null; /** * Shipping cost. Optional. * @var float */ protected $_shipping = null; /** * Total charge amount. * @var float */ protected $_total = 0.00; /** * The payment object entity. * @var \LinkPoint\Payment_CreditCard|\LinkPoint\Payment_Cheque */ protected $_paymentObject = null; /** * An (optional) item list to send along to the payment processor * * A multi-dimensional array in the following format: * array( * id => array( * id => string - (req) * sku => string - (opt) [internal] * description => string - (opt) * price => float - (req) * quantity => integer - (req) * options => array( - (opt) * array( * name => string * value => string * ), [...] * ) * ), [...] * ) * @var array */ protected $_items = array(); /** * The billing address for the payment. * @var \LinkPoint\Address */ protected $_billingAddress; /** * The shipping address for the payment. Optional. * @var \LinkPoint\Address */ protected $_shippingAddress = null; /** * Note about the transaction. Optional. * @var string */ protected $_note = null; /** * Referer information, for marketing purposes. Optional. * @var string */ protected $_referer = null; /** * Transaction reference number * @var int|string */ protected $_referenceNumber = null; /** * The result XML from the processor. * @var string */ protected $_result = null; /** * Whether or not the transaction succeeded. * @var boolean */ public $hasError = false; /** * @var \LinkPoint\LinkPoint instance */ protected $_instance = null; /** * Terminal Type (default: UNSPECIFIED) * @var string */ protected $_termType = LinkPoint::TERMINAL_UNSPECIFIED; /** * Transaction Origin (default: ECI) * @var string */ protected $_txOrigin = LinkPoint::ORIGIN_ECI; /** * Transaction Type * @var string */ protected $_txType = LinkPoint::TYPE_SALE; protected $_transactionMode = LinkPoint::MODE_LIVE; /** * Constructor * * @todo Create an instantiator as LinkPoint::forge() for multi-gateway usage(?) */ public function __construct($config = array()) { if (empty($config['store_name'])) { throw new Exception('Store name not specified.'); } if (empty($config['key_file']) || !file_exists($config['key_file'])) { throw new Exception('PEM key/certificate file not found'); } $this->setStoreName($config['store_name']); $this->setKeyFile($config['key_file']); if (is_int($config['timeout'])) { $this->setTimeout($config['timeout']); } } public function setTransactionMode($mode) { if (!in_array($mode, array(LinkPoint::MODE_LIVE, LinkPoint::MODE_TEST))) { throw new Exception('Unknown transaction mode.'); } $this->_transactionMode = $mode; return $this; } public function getTransactionMode() { return $this->_transactionMode; } /** * Sets the store name. * * @param string $storeName * @return \LinkPoint\LinkPoint * @throws \LinkPoint\Exception */ public function setStoreName($storeName = null) { if (empty($storeName)) { throw new Exception('Store name must not be empty'); } $this->_storeName = $storeName; return $this; } /** * Returns the store name. * * @return string */ public function getStoreName() { return $this->_storeName; } /** * Sets the digital certificate's filename. * * @param string $keyFile * @return \LinkPoint\LinkPoint * @throws \LinkPoint\Exception */ public function setKeyFile($keyFile = null) { if (empty($keyFile) || !file_exists($keyFile)) { throw new Exception('Store PEM key/certificate file does not exist'); } $this->_keyFile = $keyFile; return $this; } /** * Returns the digital certificate's filename. * * @return string */ public function getKeyFile() { return $this->_keyFile; } /** * Sets the host name. * * @param string $host * @return \LinkPoint\LinkPoint * @throws \LinkPoint\Exception */ public function setHost($host) { if (gethostbyname($host) == $host) { throw new Exception('Host is invalid'); } $this->_host = $host; return $this; } /** * Returns the host name. * * @return string */ public function getHost() { return $this->_host; } /** * Sets the port number. * * @param int $port * @return \LinkPoint\LinkPoint * @throws \LinkPoint\Exception */ public function setPort($port) { if (!is_numeric($port) || ($port < 1) || ($port > 65535)) { throw new Exception('Port is invalid'); } $this->_port = $port; return $this; } /** * Returns the port number. * * @return int */ public function getPort() { return $this->_port; } /** * Sets the connection timeout. * * @param int $timeout * @return \LinkPoint\LinkPoint * @throws \LinkPoint\Exception */ public function setTimeout($timeout) { $this->timeout = $timeout; return $this; } /** * Returns the connection timeout. * * @return int */ public function getTimeout() { return $this->timeout; } /** * Sets the terminal type * * @param string $term_type One of STANDALONE, POS, UNATTENDED, UNSPECIFIED * @return \LinkPoint\LinkPoint * @throws \LinkPoint\Exception */ public function setTerminalType($term_type = LinkPoint::TERMINAL_UNSPECIFIED) { if (!in_array(strtoupper($term_type), array(LinkPoint::TERMINAL_STANDALONE, LinkPoint::TERMINAL_POINTOFSALE, LinkPoint::TERMINAL_UNATTENDED, LinkPoint::TERMINAL_UNSPECIFIED))) { throw new Exception('Unknown terminal type: '.$term_type); } $this->_termType = $term_type; return $this; } /** * Gets the terminal type * * @return string */ public function getTerminalType() { return $this->_termType; } /** * Sets the transaction origin * * @param string $origin One of RETAIL, TELEPHONE, MOTO, ECI * @return \LinkPoint\LinkPoint * @throws \LinkPoint\Exception */ public function setTransactionOrigin($origin = LinkPoint::ORIGIN_ECI) { if (!in_array(strtoupper($origin), array(LinkPoint::ORIGIN_RETAIL, LinkPoint::ORIGIN_TELEPHONE, LinkPoint::ORIGIN_MAILORDER, LinkPoint::ORIGIN_ECI))) { throw new Exception('Unknown transaction origin: '.$origin); } $this->_txOrigin = $origin; return $this; } /** * Gets the transaction origin * * @return string */ public function getTransactionOrigin() { return $this->_txOrigin; } /** * Sets the transaction type * * @param string $tx_type One of SALE, PREAUTH, POSTAUTH, VOID or CREDIT * @return \LinkPoint\LinkPoint * @throws \LinkPoint\Exception */ public function setTransactionType($tx_type = LinkPoint::TYPE_SALE) { if (!in_array(strtolower($tx_type), array(LinkPoint::TYPE_SALE, LinkPoint::TYPE_AUTH, LinkPoint::TYPE_CAPTURE, LinkPoint::TYPE_VOID, LinkPoint::TYPE_CREDIT))) { throw new Exception('Unknown transaction type: '.$tx_type); } $this->_txType = $tx_type; return $this; } /** * Gets the transaction type * * @return string */ public function getTransactionType() { return $this->_txType; } /** * Sets whether or not to require AVS checks * * @param bool $avs True/False = Required/Optional * @return \LinkPoint\LinkPoint * @throws \LinkPoint\Exception */ public function setAvs($avs = false) { if (!is_bool($avs)) { throw new Exception('AVS Check toggle must be a boolean (true or false) value'); } $this->_avsRequired = $avs; return $this; } /** * Gets whether or not to require AVS checks * * @return bool */ public function getAvs() { return $this->_avsRequired; } /** * Sets the order ID. * * @param string $orderId * @return \LinkPoint\LinkPoint * @throws \LinkPoint\Exception */ public function setOrderId($orderId = null) { if (strlen($orderId) > 100) { throw new Exception('Order ID is over 100 characters'); } if (preg_match('/[^0-9a-z\-\_]/i', $orderId) !== 0) { throw new Exception('Order ID can only contain numbers, letters, hyphens and underscores'); } $this->_orderId = $orderId; return $this; } /** * Returns the order ID. * * @return string */ public function getOrderId() { return $this->_orderId; } /** * Sets the invoice number. * * @param string $invoiceId * @return \LinkPoint\LinkPoint * @throws \LinkPoint\Exception */ public function setInvoiceId($invoiceId = null) { if (strlen($invoiceId) > 48) { throw new Exception('Invoice ID is over 48 characters'); } if (preg_match('/[^0-9a-z]/i', $invoiceId) !== 0) { throw new Exception('Invoice ID can only contain numbers and letters'); } $this->_invoiceId = $invoiceId; return $this; } /** * Returns the invoice number. * * @return string */ public function getInvoiceId() { return $this->_invoiceId; } /** * Sets the customer-provided purchase order number. * * @param string $poId * @return \LinkPoint\LinkPoint * @throws \LinkPoint\Exception */ public function setPurchaseOrder($poId) { if (strlen($poId) > 128) { throw new Exception('Purchase Order ID is over 128 characters'); } if (preg_match('/[^0-9a-z]/i', $poId) !== 0) { throw new Exception('Purchase Order ID can only contain numbers and letters'); } $this->_purchaseOrder = $poId; return $this; } /** * Returns the customer-provided purchase order number. * * @return string */ public function getPurchaseOrder() { return $this->_purchaseOrder; } /** * Sets the tax-exempt status of the order. * * @param boolean $status * @return \LinkPoint\LinkPoint * @throws \LinkPoint\Exception */ public function setTaxExemptStatus($status = false) { if (!is_bool($status)) { throw new Exception('Tax Exempt Status is a boolean (true or false)'); } $this->_taxExempt = (bool) $status; return $this; } /** * Returns the tax-exempt status of the order. * * @return boolean */ public function getTaxExemptStatus() { return $this->_taxExempt; } /** * Set the purchase subtotal. Optional. * * @param float $subtotal * @return \LinkPoint\LinkPoint * @throws \LinkPoint\Exception */ public function setSubtotal($subtotal = 0.00) { $precision = strlen(substr((string) $subtotal, (strpos((string) $subtotal, '.') + 1))); $number = strlen(substr((string) $subtotal, 0, strpos((string) $subtotal, '.'))); if (($number > 14) || ($precision > 2)) { throw new Exception('Monetary amounts cannot be over 14 digits in length or over 2 decimal places'); } $this->_subtotal = $subtotal; return $this; } /** * Returns the purchase subtotal. * * @return float|integer */ public function getSubtotal() { return $this->_subtotal; } /** * Clears the purchase subtotal. * * @return \LinkPoint\LinkPoint */ public function clearSubtotal() { $this->_subtotal = null; return $this; } /** * Set the tax amount. * * @param float $tax * @return \LinkPoint\LinkPoint * @throws \LinkPoint\Exception */ public function setTax($tax) { $precision = strlen(substr((string) $tax, (strpos((string) $tax, '.') + 1))); $number = strlen(substr((string) $tax, 0, strpos((string) $tax, '.'))); if (($number > 14) || ($precision > 2)) { throw new Exception('Monetary amounts cannot be over 14 digits in length or over 2 decimal places'); } $this->_tax = $tax; return $this; } /** * Returns the tax amount. * * @return float|integer */ public function getTax() { return $this->_tax; } /** * Clears the tax amount. * * @return \LinkPoint\LinkPoint */ public function clearTax() { $this->_tax = 0.00; return $this; } /** * Set the VAT amount. Optional. * * @param float $vat * @return \LinkPoint\LinkPoint * @throws \LinkPoint\Exception */ public function setVat($vat) { $precision = strlen(substr((string) $vat, (strpos((string) $vat, '.') + 1))); $number = strlen(substr((string) $vat, 0, strpos((string) $vat, '.'))); if (($number > 14) || ($precision > 2)) { throw new Exception('Monetary amounts cannot be over 14 digits in length or over 2 decimal places'); } $this->_vat = $vat; return $this; } /** * Returns the VAT amount. * * @return float|integer */ public function getVat() { return $this->_vat; } /** * Clears the VAT. * * @return \LinkPoint\LinkPoint */ public function clearVat() { $this->_vat = null; return $this; } /** * Set the shipping amount. Optional. * * @param float $shipping * @return \LinkPoint\LinkPoint * @throws \LinkPoint\Exception */ public function setShipping($shipping) { $precision = strlen(substr((string) $shipping, (strpos((string) $shipping, '.') + 1))); $number = strlen(substr((string) $shipping, 0, strpos((string) $shipping, '.'))); if (($number > 14) || ($precision > 2)) { throw new Exception('Monetary amounts cannot be over 14 digits in length or over 2 decimal places'); } $this->_shipping = $shipping; return $this; } /** * Returns the shipping amount. * * @return float|integer */ public function getShipping() { return $this->_shipping; } /** * Clears the shipping amount. * * @return \LinkPoint\LinkPoint */ public function clearShipping() { $this->_shipping = null; return $this; } /** * Set the purchase total. * * @param float $total * @return \LinkPoint\LinkPoint * @throws \LinkPoint\Exception */ public function setTotal($total) { $precision = strlen(substr((string) $total, (strpos((string) $total, '.') + 1))); $number = strlen(substr((string) $total, 0, strpos((string) $total, '.'))); if (($number > 14) || ($precision > 2)) { throw new Exception('Monetary amounts cannot be over 14 digits in length or over 2 decimal places'); } if ($total <= 0.00) { throw new Exception('Total must over zero'); } $this->_total = $total; return $this; } /** * Returns the total amount. * * @return float|integer */ public function getTotal() { return $this->_total; } /** * Clears the total amount. * * @return \LinkPoint\LinkPoint */ public function clearTotal() { $this->_total = 0.00; return $this; } /** * Add an item to the list of items in this purchase. * * A multi-dimensional array in the following format: * array( * id => string - (req) * description => string - (opt) * price => float - (req) * quantity => integer - (req) * options => array( - (opt) * array( * name => string - (req) * value => string - (req) * ), [...] * ) * ) * * @param array $item * @return \LinkPoint\LinkPoint * @throws \LinkPoint\Exception */ public function addItem(array $item) { foreach(array('id','price','quantity') as $key) { if (!isset($item[$key])) { throw new Exception('Required attribute not present: '.$key); } } if (isset($item['options']) && (empty($item['options']) || !is_array($item['options']))) { unset($item['options']); } elseif (isset($item['options']) && is_array($item['options'])) { foreach($item['options'] as $option) { if (!isset($option['name']) || empty($option['name'])) { throw new Exception('Option names on items are required'); } if (!isset($option['value']) || empty($option['value'])) { throw new Exception('Option values on items are required'); } } } $this->_items[$item['id']] = $item; return $this; } /** * Add multiple items to the list of items in this purchase. * * @param array $items * @return \LinkPoint\LinkPoint * @throws \LinkPoint\Exception */ public function addItems(array $items) { if (!empty($items) && is_array($items)) { foreach($items as $item) { $this->addItem($item); } } return $this; } /** * Returns whether or not an item exists in this purchase. * * @param string $id * @return boolean */ public function hasItem($id) { return isset($this->_items[$id]); } /** * Returns a single item, or FALSE if item doesn't exist. * * @param string $id * @return array|boolean */ public function getItem($id) { return ($this->hasItem($id) ? $this->_items[$id] : false); } /** * Returns all items. * * @return array */ public function getItems() { return $this->_items; } /** * Remove an item from the list of items in this purchase. * * @param string $id * @return \LinkPoint\LinkPoint */ public function removeItem($id) { if ($this->hasItem($id)) { unset($this->_items[$id]); } return $this; } /** * Remove multiple items from the list of items in this purchase. * * @param array $ids * @return \LinkPoint\LinkPoint * @throws \LinkPoint\Exception */ public function removeItems(array $ids) { foreach($ids as $id) { $this->removeItem($id); } return $this; } /** * Remove all items from the list of items in this purchase. * * @return \LinkPoint\LinkPoint */ public function clearItems() { $this->_items = array(); return $this; } /** * Get items count * * @return int */ public function getItemCount() { return count($this->_items); } /** * Set transaction date parameter * * @param int $tdate Unix timestamp from previous transaction * @return \LinkPoint\LinkPoint */ public function setTransactionDate($tdate) { $this->_tdate = $tdate; return $this; } /** * Get the transaction date * * @return int|bool */ public function getTransactionDate() { if ($this->_tdate > 0) { return $this->_tdate; } else { return false; } } /** * Sets the transaction note. * * @param string $note * @return \LinkPoint\LinkPoint */ public function setNote($note) { $this->_note = $note; return $this; } /** * Returns the transaction note. * * @return string */ public function getNote() { return $this->_note; } /** * Clear the transaction note. * * @return \LinkPoint\LinkPoint */ public function clearNote() { $this->_note = null; return $this; } /** * Sets the transaction referer. * * @param string $referer * @return \LinkPoint\LinkPoint * @throws \LinkPoint\Exception */ public function setReferer($referer) { if (strlen($referer) > 1024) { throw new Exception('Referer field cannot be over 1024 alphanumeric characters'); } if (preg_match('/[^0-9a-z]/i', $referer) !== 0) { throw new Exception('Only alphanumeric characters are allowed on the referer field'); } $this->_referer = $referer; return $this; } /** * Returns the transaction referrr. * * @return string */ public function getReferer() { return $this->_referer; } /** * Clears the transaction referer. * @return \LinkPoint\LinkPoint */ public function clearReferer() { $this->_referer = null; return $this; } /** * Sets the transaction reference number * * @param int|string $ref Transaction Reference Number * @return \LinkPoint\LinkPoint * @throws \LinkPoint\Exception */ public function setReferenceNumber($ref) { if (strlen($ref) > 128) { throw new Exception('Transaction Reference Number must not be over 128 characters'); } if (preg_match('/[^0-9a-z]/i', $ref) !== 0) { throw new Exception('Transaction Reference Number must contain only letters and numbers'); } $this->_referenceNumber = $ref; return $this; } /** * Gets the transaction reference number * * @return string */ public function getReferenceNumber() { return $this->_referenceNumber; } /** * Sets the payment object being used to pay for the transaction. * * @param \LinkPoint\Payment\CreditCard|\LinkPoint\Payment\Cheque * @return \LinkPoint\LinkPoint * @throws \LinkPoint\Exception */ public function setPaymentObject($object) { if ($object instanceof Payment_CreditCard) { // We need either track or ccnum, exp_month/exp_year if (!$object->getTrack()) { if (!$object->getCcNum() || !$object->getExp('month') | !$object->getExp('year')) { throw new Exception('Either track data or card number with expiration month/year needs to be used'); } } // cvm is optional, but wether it is used or not, cvmindicator needs to reflect this if (!$object->getCvm()) { if (!in_array($object->getCvmState(), $object->getCvmStates())) { throw new Exception('The CVM/CVV2 indicator is incorrect (ie. CVM/CVV2 not provided, but no reason given)'); } } elseif ($object->getCvm()) { if ($object->getCvmState() != 'provided') { throw new Exception('The CVM/CVV2 indicator is incorrect (ie. CVM/CVV2 provided, but reason is not "provided")'); } } } elseif ($object instanceof Payment_Cheque) { // Everything but cheque number is required if (!$object->getAccountNumber()) { throw new Exception('The account number is required'); } if (!$object->getRoutingNumber()) { throw new Exception('The routing number is required'); } if (!$object->getAccountType() || in_array($object->getAccountType(), $object->getAccountTypes())) { throw new Exception('The account type is required'); } if ($object->getChequeNumber() && !is_numeric($object->getChequeNumber())) { throw new Exception('The cheque number is invalid'); } if (!$object->getBank('name') || !$object->getBank('province')) { throw new Exception('The Bank details (name, state/province) are required'); } if (!$object->getIdentification('number') || !$object->getIdentification('province')) { throw new Exception('The ID details (name, state/province) are required'); } } else { throw new Exception('Invalid or Unknown payment object provided'); } $this->_paymentObject = $object; return $this; } /** * Removes the payment object. * * @return \LinkPoint\LinkPoint */ public function clearPaymentObject() { $this->_paymentObject = null; return $this; } /** * Returns the payment object. * * @return \LinkPoint\Payment_Cheque|\LinkPoint\Payment_CreditCard */ public function getPaymentObject() { return $this->_paymentObject; } /** * Sets the billing address. * * @param \LinkPoint\Address $address * @return \LinkPoint\LinkPoint * @throws \LinkPoint\Exception * @todo Validation * @todo Verification (ie. USPS Address Lookup) */ public function setBillingAddress(Address $address) { $this->_billingAddress = $address; return $this; } /** * Returns the billing address. * * @return \LinkPoint\Address */ public function getBillingAddress() { return $this->_billingAddress; } /** * Clears the billing address. * * @return \LinkPoint\Address */ public function clearBillingAddress() { $this->_billingAddress = null; return $this; } /** * Sets the shipping address. Optional. * * @param \LinkPoint\Address $address * @return \LinkPoint\LinkPoint * @throws \LinkPoint\Exception * @todo Validation * @todo Verification (ie. USPS Address Lookup) */ public function setShippingAddress(Address $address) { $this->_shippingAddress = $address; return $this; } /** * Returns the shipping address. * * @return \LinkPoint\Address */ public function getShippingAddress() { return $this->_shippingAddress; } /** * Clears the shipping address. * * @return \LinkPoint\LinkPoint */ public function clearShippingAddress() { $this->_shippingAddress = null; return $this; } /** * Generate and return a random string * * The default string returned is 17 alphanumeric characters. * * This version is modified to mimic PayPal's transaction IDs. * * @author Aidan Lister * @author Elisamuel Resto * @link http://aidanlister.com/repos/v/function.str_rand.php * @license Public Domain * * @param int $length Length of string to be generated * @return string */ function genCode($length = 17) { // Possible seeds $seedings['alpha'] = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; $seedings['numeric'] = '0123456789'; $seedings['alphanum'] = $seedings['alpha'].$seedings['numeric']; // Seed generator list($usec, $sec) = explode(' ', microtime()); mt_srand(((float) $sec + ((float) $usec * 100000))); // Generate $str = ''; $alphac = 0; for($i = 0; $length > $i; $i++) { if ((mt_rand(0, 100) % 2) == 1) { $seeds = $seedings['alphanum']; } else { $seeds = $seedings['numeric']; } $piece = $seeds{mt_rand(0, (strlen($seeds) - 1))}; if (!is_int($piece)) { $alphac++; } $str .= $piece; } return $str; } /** * Submits the current order to the payment processor. * * @param string $transactionType * @param string $transactionMode * @return boolean * @throws \LinkPoint\Exception * @todo Proper URL? DOCS say '/lpc/servlet/lppay' other modules use '/LSGSXML' */ public function process($transactionType = null, $transactionMode = null) { if (null !== $transactionType) { $this->setTransactionType($transactionType); } if (null !== $transactionMode) { $this->setTransactionMode($transactionMode); } if ($this->getTransactionMode() == LinkPoint::MODE_LIVE) { $this->setHost('secure.linkpt.net'); } else { $this->setHost('staging.linkpt.net'); } $this->_ch = curl_init(); $options = array( CURLOPT_RETURNTRANSFER => true, CURLOPT_URL => 'https://'.$this->getHost().':'.$this->getPort().'/lpc/servlet/lppay', //CURLOPT_URL => 'https://'.$this->getHost().':'.$this->getPort().'/LSGSXML', CURLOPT_TIMEOUT => $this->getTimeout(), CURLOPT_SSLCERT => $this->getKeyFile(), CURLOPT_POST => true, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_HTTPHEADER => array('Content-Type: text/xml'), CURLOPT_SSL_VERIFYHOST => 2, CURLOPT_POSTFIELDS => $this->_toXML(), CURLOPT_PORT => $this->getPort() ); curl_setopt_array($this->_ch, $options); $this->_result = curl_exec($this->_ch); $this->hasError = curl_errno($this->_ch); curl_close($this->_ch); return (($this->hasError > 0) ? false : true); } /** * Get Result * * @return array|bool */ public function getResult() { $result = $this->_xml2array('' . $this->_result . ''); return $result; } /** * Converts an xml string to an Array * * @param string $xml * @return array */ public function _xml2array($xml) { $dom = new \DOMDocument; $dom->loadXml($xml); $result = $this->_processXmlNode($dom->documentElement); foreach ($result as &$value) { if (!$value) { $value = null; } } return $result; } protected function _processXmlNode(\DOMNode $node) { $output = array(); switch ($node->nodeType) { case XML_CDATA_SECTION_NODE: case XML_TEXT_NODE: $output = trim($node->textContent); break; case XML_ELEMENT_NODE: for ($i = 0, $m = $node->childNodes->length; $i < $m; $i++) { $child = $node->childNodes->item($i); $v = $this->_processXmlNode($child); if (isset($child->tagName)) { $t = $child->tagName; if (!isset($output[$t])) { $output[$t] = array(); } $output[$t][] = $v; } elseif ($v) { $output = (string) $v; } } if (is_array($output)) { if ($node->attributes->length) { $a = array(); foreach ($node->attributes as $attrName => $attrNode) { $a[$attrName] = (string) $attrNode->value; } $output['@attributes'] = $a; } foreach ($output as $t => $v) { if (is_array($v) && count($v) == 1 && $t != '@attributes') { $output[$t] = $v[0]; } } } break; } return $output; } /** * Creates an XML request for the API * * @return string * @throws \LinkPoint\Exception */ public function _toXML() { $doc = new \DOMDocument('1.0', 'UTF-8'); $doc->formatOutput = true; // Root Element $xml = $doc->createElement('order'); // Merchant Info $mi = $doc->createElement('merchantinfo'); $mi->appendChild($doc->createElement('configfile', $this->getStoreName())); $mi->appendChild($doc->createElement('keyfile', $this->getStoreName().'.pem')); $mi->appendChild($doc->createElement('host', $this->getHost())); $mi->appendChild($doc->createElement('port', $this->getPort())); $xml->appendChild($mi); unset($mi); // Order Options $oo = $doc->createElement('orderoptions'); $oo->appendChild($doc->createElement('ordertype', $this->getTransactionType())); //$oo->appendChild($doc->createElement('result', 'GOOD')); $xml->appendChild($oo); unset($oo); if (in_array($this->getTransactionType(), array(LinkPoint::TYPE_SALE, LinkPoint::TYPE_AUTH))) { // Transaction Notes if ($this->getNote() || $this->getReferer()) { $note = $doc->createElement('notes'); if ($this->getNote()) { $note->appendChild($doc->createElement('comments', $this->getNote())); } if ($this->getReferer()) { $note->appendChild($doc->createElement('referred', $this->getReferer())); } $xml->appendChild($note); unset($note); } // Items if ($this->getItemCount() > 0) { $items = $doc->createElement('items'); foreach($this->getItems() as $_item) { $item = $doc->createElement('item'); $item->appendChild($doc->createElement('id', $_item['id'])); $item->appendChild($doc->createElement('price', $_item['price'])); $item->appendChild($doc->createElement('quantity', $_item['quantity'])); if (isset($_item['description'])) { $item->appendChild($doc->createElement('description', $_item['description'])); } if (isset($_item['options'])) { $options = $doc->createElement('options'); foreach($_item['options'] as $_option) { $option = $doc->createElement('option'); $option->appendChild($doc->createElement('name', $_option['name'])); $option->appendChild($doc->createElement('value', $_option['value'])); $options->appendChild($option); unset($option); } $item->appendChild($options); unset($options, $_option); } $items->appendChild($item); unset($item); } $xml->appendChild($items); unset($items, $_item); } // Billing Address if ($this->getBillingAddress() instanceof Address) { $bnode = $doc->createElement('billing'); $b = $this->getBillingAddress(); // name if ($b->getName()) { $bnode->appendChild($doc->createElement('name', $b->getName())); } // address1 if ($b->getAddress(1)) { $bnode->appendChild($doc->createElement('address1', $b->getAddress(1))); } // address2 if ($b->getAddress(2)) { $bnode->appendChild($doc->createElement('address2', $b->getAddress(1))); } // addrnum if ($b->getAddressNumber()) { $bnode->appendChild($doc->createElement('addrnum', $b->getAddressNumber())); } // city if ($b->getCity()) { $bnode->appendChild($doc->createElement('city', $b->getCity())); } // state if ($b->getProvince()) { $bnode->appendChild($doc->createElement('state', $b->getProvince())); } // zip if ($b->getPostCode()) { $bnode->appendChild($doc->createElement('zip', $b->getPostCode())); } // phone if ($b->getPhone()) { $bnode->appendChild($doc->createElement('phone', $b->getPhone())); } // fax if ($b->getFax()) { $bnode->appendChild($doc->createElement('fax', $b->getFax())); } // email if ($b->getEmail()) { $bnode->appendChild($doc->createElement('email', $b->getEmail())); } // userid if ($b->getUserId()) { $bnode->appendChild($doc->createElement('userid', $b->getUserId())); } $xml->appendChild($bnode); unset($bnode, $b); } // Shipping Address if ($this->getShippingAddress() instanceof Address) { $snode = $doc->createElement('shipping'); $s = $this->getShippingAddress(); // name if ($s->getName()) { $snode->appendChild($doc->createElement('name', $s->getName())); } // address1 if ($s->getAddress(1)) { $snode->appendChild($doc->createElement('address1', $s->getAddress(1))); } // address2 if ($s->getAddress(2)) { $snode->appendChild($doc->createElement('address2', $s->getAddress(1))); } // city if ($s->getCity()) { $snode->appendChild($doc->createElement('city', $s->getCity())); } // state if ($s->getProvince()) { $snode->appendChild($doc->createElement('state', $s->getProvince())); } // zip if ($s->getPostCode()) { $snode->appendChild($doc->createElement('zip', $s->getPostCode())); } // phone if ($s->getPhone()) { $snode->appendChild($doc->createElement('phone', $s->getPhone())); } // fax if ($s->getFax()) { $snode->appendChild($doc->createElement('fax', $s->getFax())); } // email if ($s->getEmail()) { $snode->appendChild($doc->createElement('email', $s->getEmail())); } // items if ($s->getItemCount()) { $snode->appendChild($doc->createElement('items', $s->getItemCount())); } // weight if ($s->getWeight()) { $snode->appendChild($doc->createElement('weight', $s->getWeight())); } // carrier if ($s->getCarrier()) { $snode->appendChild($doc->createElement('carrier', $s->getCarrier())); } // carrier if ($s->getTotal()) { $snode->appendChild($doc->createElement('total', $s->getTotal())); } $xml->appendChild($snode); unset($snote, $s); } } // Transaction Details $td = $doc->createElement('transactiondetails'); $td->appendChild($doc->createElement('oid', $this->getOrderId())); if (in_array($this->getTransactionType(), array(LinkPoint::TYPE_SALE, LinkPoint::TYPE_AUTH))) { $td->appendChild($doc->createElement('taxexempt', ($this->getTaxExemptStatus() ? 'Y' : 'N'))); $td->appendChild($doc->createElement('transactionorigin', $this->getTransactionOrigin())); $td->appendChild($doc->createElement('terminaltype', $this->getTerminalType())); } if ($this->getPurchaseOrder()) { $td->appendChild($doc->createElement('ponumber', $this->getPurchaseOrder())); } if ($this->getInvoiceId()) { $td->appendChild($doc->createElement('invoice_number', $this->getInvoiceId())); } if ($this->getReferenceNumber()) { $td->appendChild($doc->createElement('reference_number', $this->getReferenceNumber())); } if (isset($_SERVER['REMOTE_ADDR']) && !in_array($this->getTerminalType(), array(LinkPoint::TERMINAL_POINTOFSALE, LinkPoint::TERMINAL_STANDALONE, LinkPoint::TERMINAL_UNATTENDED))) { $td->appendChild($doc->createElement('ip', $_SERVER['REMOTE_ADDR'])); } if (in_array($this->getTransactionType(), array(LinkPoint::TYPE_VOID, LinkPoint::TYPE_CREDIT, LinkPoint::TYPE_CAPTURE)) && $this->getTransactionDate()) { $td->appendChild($doc->createElement('tdate', $this->getTransactionDate())); } $xml->appendChild($td); unset($td); // Payment Data $pa = $doc->createElement('payment'); if ($this->getTotal()) { $pa->appendChild($doc->createElement('chargetotal', $this->getTotal())); } else { throw new Exception('Transaction total is required'); } if ($this->getSubtotal()) { $pa->appendChild($doc->createElement('subtotal', $this->getSubtotal())); } if ($this->getTax()) { $pa->appendChild($doc->createElement('tax', $this->getTax())); } elseif (!$this->getTax() && in_array($this->getTransactionType(), array(LinkPoint::TYPE_SALE, LinkPoint::TYPE_AUTH))) { $pa->appendChild($doc->createElement('tax', 0)); } if (!$this->getTax() && $this->getVat()) { $pa->appendChild($doc->createElement('vattax', $this->getVat())); } if ($this->getShipping()) { $pa->appendChild($doc->createElement('shipping', $this->getShipping())); } $xml->appendChild($pa); unset($pa); if (in_array($this->getTransactionType(), array(LinkPoint::TYPE_SALE, LinkPoint::TYPE_AUTH))) { // Payment Method $p = $this->getPaymentObject(); if (!($p instanceof Payment_CreditCard) && !($p instanceof Payment_Cheque)) { throw new Exception('Payment method is required'); } /** @var \DOMElement $pnode */ $pnode = null; if ($p instanceof Payment_CreditCard) { $pnode = $doc->createElement(Payment_CreditCard::XML_ENTITY); if (!$p->getTrack()) { // cc num $pnode->appendChild($doc->createElement('cardnumber', $p->getCcNum())); // cc exp month $pnode->appendChild($doc->createElement('cardexpmonth', str_pad($p->getExp('month'), 2, '0', STR_PAD_LEFT))); // cc exp year $pnode->appendChild($doc->createElement('cardexpyear', substr($p->getExp('year'), -2, 2))); } else { // credit card magnetic stripe data $pnode->appendChild($doc->createElement('track', $p->getTrack())); } // cvm if ($p->getCvm() != false) { $pnode->appendChild($doc->createElement('cvmvalue', $p->getCvm())); } // cvm state $pnode->appendChild($doc->createElement('cvmindicator', $p->getCvmState())); } elseif ($p instanceof Payment_Cheque) { $pnode = $doc->createElement(Payment_Cheque::XML_ENTITY); // account number $pnode->appendChild($doc->createElement('account', $p->getAccountNumber())); // routing number $pnode->appendChild($doc->createElement('routing', $p->getRoutingNumber())); // cheque number if ($p->getChequeNumber()) { $pnode->appendChild($doc->createElement('checknumber', $p->getChequeNumber())); } // account type $pnode->appendChild($doc->createElement('accounttype', $p->getAccountType())); // bank name $pnode->appendChild($doc->createElement('bankname', $p->getBank('name'))); // bank province $pnode->appendChild($doc->createElement('bankstate', $p->getBank('province'))); // id number $pnode->appendChild($doc->createElement('dl', $p->getIdentification('number'))); // id province $pnode->appendChild($doc->createElement('dlstate', $p->getIdentification('province'))); // void if ($p->getVoid()) { $pnode->appendChild($doc->createElement('void', $p->getVoid())); } // ssn if ($p->getSsn()) { $pnode ->appendChild($doc->createElement('ssn', $p->getSsn())); } } $xml->appendChild($pnode); unset($pnode, $p); } $doc->appendChild($xml); unset($xml); return $doc->saveXML(); } }