├── src ├── php │ └── Rbs │ │ └── Payment │ │ ├── Template │ │ ├── LoaderInterface.php │ │ ├── Loader │ │ │ ├── String.php │ │ │ └── Filesystem.php │ │ └── Parser.php │ │ ├── Factory.php │ │ ├── Fields.php │ │ ├── templates │ │ └── basic.html │ │ ├── Http │ │ └── Client.php │ │ └── Gateway │ │ ├── TwoCheckout.php │ │ ├── Paypal.php │ │ └── AbstractGateway.php └── tests │ └── unit-tests │ ├── bootstrap.php │ ├── phpunit.xml │ └── Rbs │ └── Payment │ ├── FactoryTest.php │ └── Gateway │ └── PaypalTest.php ├── examples ├── twocheckout.php └── paypal.php └── README.md /src/php/Rbs/Payment/Template/LoaderInterface.php: -------------------------------------------------------------------------------- 1 | 10 | 11 | -------------------------------------------------------------------------------- /src/php/Rbs/Payment/Template/Loader/String.php: -------------------------------------------------------------------------------- 1 | source = $source; 12 | } 13 | 14 | public function load() 15 | { 16 | if (!empty($this->source)) { 17 | throw new \InvalidArgumentException("Provided source is empty."); 18 | } 19 | 20 | return $this->source; 21 | } 22 | } -------------------------------------------------------------------------------- /src/php/Rbs/Payment/Factory.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf("Rbs\\Payment\\Gateway\\AbstractGateway", $factory); 9 | } 10 | 11 | /** 12 | * @expectedException InvalidArgumentException 13 | */ 14 | public function testDoesNotLoadInvalidGateway() 15 | { 16 | $factory = \Rbs\Payment\Factory::factory('Invalid'); 17 | } 18 | } -------------------------------------------------------------------------------- /src/php/Rbs/Payment/Template/Parser.php: -------------------------------------------------------------------------------- 1 | loader = $loader; 12 | } 13 | 14 | public function parse($placeholders = array()) 15 | { 16 | $output = $this->loader->load(); 17 | 18 | foreach ($placeholders as $key => $value) { 19 | $needle = '{' . $key . '}'; 20 | $output = str_replace($needle, $value, $output); 21 | } 22 | 23 | return $output; 24 | } 25 | } -------------------------------------------------------------------------------- /src/php/Rbs/Payment/Template/Loader/Filesystem.php: -------------------------------------------------------------------------------- 1 | source = $source; 12 | } 13 | 14 | public function load() 15 | { 16 | if (!file_exists($this->source)) { 17 | throw new \InvalidArgumentException("Provided file source does not exist."); 18 | } 19 | 20 | $contents = file_get_contents($this->source); 21 | return $contents; 22 | } 23 | } -------------------------------------------------------------------------------- /src/php/Rbs/Payment/Fields.php: -------------------------------------------------------------------------------- 1 | fields)) { 12 | return $this->fields[$key]; 13 | } 14 | } 15 | 16 | public function __set($key, $value) 17 | { 18 | $this->fields[$key] = $value; 19 | } 20 | 21 | public function __isset($key) 22 | { 23 | return array_key_exists($key, $this->fields); 24 | } 25 | 26 | public function getAll() 27 | { 28 | return $this->fields; 29 | } 30 | } -------------------------------------------------------------------------------- /examples/twocheckout.php: -------------------------------------------------------------------------------- 1 | setAccountIdentifier(array('sid' => 1234567, 'secret' => 'tango')); 11 | $twoCheckout->setCurrency('USD'); 12 | $twoCheckout->setSingleItem('T-shirt', 4.99, "1001"); 13 | $twoCheckout->setOrderNumber(100); 14 | 15 | $twoCheckout->setReturnOnSuccessUrl('http://phpfour.com/payment/twocheckout_success.php'); 16 | 17 | $twoCheckout->proceed(); -------------------------------------------------------------------------------- /examples/paypal.php: -------------------------------------------------------------------------------- 1 | setAccountIdentifier('payment@test.com'); 11 | $paypal->setCurrency('USD'); 12 | $paypal->setSingleItem('T-shirt', 4.99, "1001"); 13 | $paypal->setOrderNumber(100); 14 | 15 | $paypal->setReturnOnSuccessUrl('http://phpfour.com/payment/paypal_success.php'); 16 | $paypal->setReturnOnFailureUrl('http://phpfour.com/payment/paypal_failure.php'); 17 | $paypal->setNotificationUrl('http://phpfour.com/payment/paypal_ipn.php'); 18 | 19 | $paypal->proceed(); -------------------------------------------------------------------------------- /src/php/Rbs/Payment/templates/basic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {pageTitle} 6 | 20 | 25 | 26 | 27 |

{pageTitle}

28 |
29 | {primaryMessage} 30 | {paymentForm} 31 |
32 | 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PHP Payment 2 | =========== 3 | 4 | A payment library for PHP that supports Paypal, Authorize.net and 2Checkout. 5 | 6 | * Easy to integrate 7 | * Supports single order checkout 8 | * Unified interface for all payment gateways 9 | * Template for redirection page 10 | 11 | Usage 12 | ===== 13 | 14 | Initiating a paypal checkout 15 | 16 | $paypal = \Rbs\Payment\Factory::factory('Paypal'); 17 | 18 | $paypal->setAccountIdentifier('emran@rightbrainsolution.com'); 19 | $paypal->setCurrency('USD'); 20 | $paypal->setSingleItem('T-shirt', 4.99, "1001"); 21 | 22 | $paypal->setReturnOnSuccessUrl('http://phpfour.com/payment/paypal_success.php'); 23 | $paypal->setReturnOnFailureUrl('http://phpfour.com/payment/paypal_failure.php'); 24 | $paypal->setNotificationUrl('http://phpfour.com/payment/paypal_ipn.php'); 25 | 26 | $paypal->proceed(); 27 | 28 | Verifying paypal IPN response 29 | 30 | $paypal = \Rbs\Payment\Factory::factory('Paypal'); 31 | $client = \Rbs\Payment\Http\Client(); 32 | 33 | $paypal->populate($_POST); 34 | $paypal->setHttpClient($client); 35 | 36 | $status = $paypal->verify(); 37 | 38 | More updates coming soon. 39 | 40 | Dependencies 41 | ============ 42 | 43 | To run the unit tests, you must have the following excellent libraries installed via PEAR: 44 | 45 | * PHPUnit - https://github.com/sebastianbergmann/phpunit 46 | * Mockery - https://github.com/padraic/mockery 47 | * Gradwell Autoloader - https://github.com/Gradwell/Autoloader -------------------------------------------------------------------------------- /src/php/Rbs/Payment/Http/Client.php: -------------------------------------------------------------------------------- 1 | url = $url; 15 | } 16 | 17 | public function setPort($port) 18 | { 19 | $this->port = $port; 20 | } 21 | 22 | public function setData($data = array()) 23 | { 24 | if (!empty($data)) { 25 | $this->data = $data; 26 | } 27 | } 28 | 29 | public function setMethod($method) 30 | { 31 | $this->method = strtoupper($method); 32 | } 33 | 34 | public function sendRequest() 35 | { 36 | $parsedUrl = parse_url($this->url); 37 | 38 | $postString = ''; 39 | foreach ($this->data as $key => $value) { 40 | $postString .= $key .'=' . urlencode(stripslashes($value)) . '&'; 41 | } 42 | 43 | $fp = fsockopen($parsedUrl['host'], $this->port, $errNum, $errStr, 30); 44 | 45 | if (!$fp) { 46 | throw new \Exception("Cannot open socket."); 47 | } 48 | 49 | fputs($fp, "{$this->method} {$parsedUrl['path']} HTTP/1.1\r\n"); 50 | fputs($fp, "Host: {$parsedUrl['host']}\r\n"); 51 | fputs($fp, "Content-type: application/x-www-form-urlencoded\r\n"); 52 | fputs($fp, "Content-length: " . strlen($postString) . "\r\n"); 53 | fputs($fp, "Connection: close\r\n\r\n"); 54 | fputs($fp, $postString . "\r\n\r\n"); 55 | 56 | $response = ''; 57 | while(!feof($fp)) { 58 | $response .= fgets($fp, 1024); 59 | } 60 | 61 | fclose($fp); 62 | 63 | return $response; 64 | } 65 | } -------------------------------------------------------------------------------- /src/php/Rbs/Payment/Gateway/TwoCheckout.php: -------------------------------------------------------------------------------- 1 | gatewayUrl = 'https://www.2checkout.com/checkout/purchase'; 10 | $this->setTestMode(false); 11 | } 12 | 13 | public function setCurrency($currency) 14 | { 15 | if (!empty($currency)) { 16 | $this->fields->tco_currency = $currency; 17 | } 18 | } 19 | 20 | public function setAccountIdentifier($identifier) 21 | { 22 | if (!empty($identifier)) { 23 | $this->fields->sid = $identifier['sid']; 24 | $this->fields->secret = $identifier['secret']; 25 | } 26 | } 27 | 28 | public function setReturnOnSuccessUrl($url) 29 | { 30 | if (!empty($url)) { 31 | $this->fields->x_Receipt_Link_URL = $url; 32 | } 33 | } 34 | 35 | public function setReturnOnFailureUrl($url) 36 | { 37 | throw new \Exception("2CO does not support a failure URL."); 38 | } 39 | 40 | public function setNotificationUrl($url) 41 | { 42 | throw new \Exception("2CO does not support an IPN URL."); 43 | } 44 | 45 | public function setCustomField($key, $value) 46 | { 47 | if (!empty($key) && !empty($value)) { 48 | $this->fields->$key = $value; 49 | } 50 | } 51 | 52 | public function setSingleItem($name, $amount, $id = null) 53 | { 54 | if (!empty($name)) { 55 | $this->fields->item_name = $name; 56 | } 57 | 58 | if (!empty($amount)) { 59 | $this->fields->total = $amount; 60 | } 61 | } 62 | 63 | public function setOrderNumber($orderNumber) 64 | { 65 | if (!empty($orderNumber)) { 66 | $this->fields->cart_order_id = $orderNumber; 67 | } 68 | } 69 | 70 | public function setTestMode($mode) 71 | { 72 | if ($mode) { 73 | $this->fields->demo = "Y"; 74 | $this->testMode = true; 75 | } 76 | } 77 | 78 | protected function validate() 79 | { 80 | $requiredFields = array( 81 | 'sid', 82 | 'tco_currency', 83 | 'x_Receipt_Link_URL', 84 | 'total', 85 | 'cart_order_id' 86 | ); 87 | 88 | foreach ($requiredFields as $field) { 89 | if (!isset($this->fields->$field)) { 90 | throw new \Exception("Required parameter {$field} has not been set."); 91 | } 92 | } 93 | 94 | return true; 95 | } 96 | 97 | public function verify() 98 | { 99 | $vendorNumber = ($this->fields->vendor_number != '') ? $this->fields->vendor_number : $this->fields->sid; 100 | $orderNumber = $this->fields->order_number; 101 | $orderTotal = $this->fields->total; 102 | 103 | // If demo mode, the order number must be forced to 1 104 | if($this->testMode) { 105 | $orderNumber = "1"; 106 | } 107 | 108 | // Calculate md5 hash as 2co formula: md5(secret_word + vendor_number + order_number + total) 109 | $key = strtoupper(md5($this->fields->secret . $vendorNumber . $orderNumber . $orderTotal)); 110 | 111 | // Verify if the key is accurate 112 | if($this->fields->key == $key || $this->fields->x_MD5_Hash == $key) { 113 | return true; 114 | } else { 115 | return false; 116 | } 117 | } 118 | } -------------------------------------------------------------------------------- /src/php/Rbs/Payment/Gateway/Paypal.php: -------------------------------------------------------------------------------- 1 | setTestMode(false); 10 | $this->setReturnMethodAsPost(); 11 | $this->setCommandAsClick(); 12 | } 13 | 14 | private function setCommandAsClick() 15 | { 16 | $this->fields->cmd = '_xclick'; 17 | } 18 | 19 | private function setReturnMethodAsPost() 20 | { 21 | $this->fields->rm = '2'; 22 | } 23 | 24 | public function setCurrency($currency) 25 | { 26 | if (!empty($currency)) { 27 | $this->fields->currency_code = $currency; 28 | } 29 | } 30 | 31 | public function setAccountIdentifier($identifier) 32 | { 33 | if (!empty($identifier)) { 34 | $this->fields->business = $identifier; 35 | } 36 | } 37 | 38 | public function setReturnOnSuccessUrl($url) 39 | { 40 | if (!empty($url)) { 41 | $this->fields->return = $url; 42 | } 43 | } 44 | 45 | public function setReturnOnFailureUrl($url) 46 | { 47 | if (!empty($url)) { 48 | $this->fields->cancel_return = $url; 49 | } 50 | } 51 | 52 | public function setNotificationUrl($url) 53 | { 54 | if (!empty($url)) { 55 | $this->fields->notify_url = $url; 56 | } 57 | } 58 | 59 | public function setCustomField($key, $value) 60 | { 61 | if (!empty($key) && !empty($value)) { 62 | $this->fields->$key = $value; 63 | } 64 | } 65 | 66 | public function setSingleItem($name, $amount, $id = null) 67 | { 68 | if (!empty($name)) { 69 | $this->fields->item_name = $name; 70 | } 71 | 72 | if (!empty($amount)) { 73 | $this->fields->amount = $amount; 74 | } 75 | 76 | if (!empty($id)) { 77 | $this->fields->item_number = $id; 78 | } 79 | } 80 | 81 | public function setOrderNumber($orderNumber) 82 | { 83 | if (!empty($orderNumber)) { 84 | $this->fields->order_number = $orderNumber; 85 | } 86 | } 87 | 88 | public function setTestMode($mode) 89 | { 90 | if ($mode) { 91 | $this->gatewayUrl = 'https://www.sandbox.paypal.com/cgi-bin/webscr'; 92 | $this->testMode = true; 93 | } else { 94 | $this->gatewayUrl = 'https://www.paypal.com/cgi-bin/webscr'; 95 | $this->testMode = false; 96 | } 97 | } 98 | 99 | protected function validate() 100 | { 101 | $requiredFields = array( 102 | 'business', 103 | 'currency_code', 104 | 'return', 105 | 'cancel_return', 106 | 'notify_url', 107 | 'item_name', 108 | 'amount' 109 | ); 110 | 111 | foreach ($requiredFields as $field) { 112 | if (!isset($this->fields->$field)) { 113 | throw new \Exception("Required parameter {$field} has not been set."); 114 | } 115 | } 116 | 117 | return true; 118 | } 119 | 120 | public function verify() 121 | { 122 | $this->fields->cmd = '_notify-validate'; 123 | 124 | $this->httpClient->setUrl($this->gatewayUrl); 125 | $this->httpClient->setPort(80); 126 | $this->httpClient->setMethod("POST"); 127 | $this->httpClient->setData($this->fields->getAll()); 128 | 129 | $response = $this->httpClient->sendRequest(); 130 | 131 | if (strpos($response, "VERIFIED") !== false) { 132 | return true; 133 | } 134 | 135 | return false; 136 | } 137 | } -------------------------------------------------------------------------------- /src/php/Rbs/Payment/Gateway/AbstractGateway.php: -------------------------------------------------------------------------------- 1 | setDefaults(); 17 | $this->init(); 18 | } 19 | 20 | private function setDefaults() 21 | { 22 | $this->fields = new \Rbs\Payment\Fields(); 23 | 24 | $this->placeholders = array( 25 | 'paymentForm' => '', 26 | 'pageTitle' => 'Proceeding to payment website...', 27 | 'redirectButton' => "Click here", 28 | 'primaryMessage' => "Please wait, your order is being processed and you will be taken to the payment website.", 29 | 'redirectMessage' => "If you are not automatically redirected to payment website within 5 seconds,

" 30 | ); 31 | 32 | $this->setTemplateLoader(new \Rbs\Payment\Template\Loader\Filesystem(__DIR__ . "/../templates/basic.html")); 33 | } 34 | 35 | public function setTemplateLoader(\Rbs\Payment\Template\LoaderInterface $loader) 36 | { 37 | if ($this->isValidTemplate($loader)) { 38 | $this->templateLoader = $loader; 39 | } else { 40 | throw new \InvalidArgumentException("Provided template loader does not return required placeholders."); 41 | } 42 | } 43 | 44 | private function isValidTemplate(\Rbs\Payment\Template\LoaderInterface $loader) 45 | { 46 | $html = $loader->load(); 47 | 48 | if (strpos($html, 'paymentForm') === false) { 49 | return false; 50 | } 51 | 52 | return true; 53 | } 54 | 55 | public function proceed() 56 | { 57 | if ($this->validate()) { 58 | $this->placeholders['paymentForm'] = $this->getPaymentForm(); 59 | $parser = new \Rbs\Payment\Template\Parser($this->templateLoader); 60 | echo $parser->parse($this->placeholders); 61 | } 62 | } 63 | 64 | private function getPaymentForm() 65 | { 66 | $formHtml = ''; 67 | $formHtml .= '
'; 68 | 69 | foreach ($this->fields->getAll() as $name => $value) { 70 | $formHtml .= ""; 71 | } 72 | 73 | $formHtml .= '

{redirectMessage}'; 74 | $formHtml .= '

'; 75 | $formHtml .= '
'; 76 | 77 | return $formHtml; 78 | } 79 | 80 | public function populate($fields = array()) 81 | { 82 | foreach ($fields as $key => $value) { 83 | $this->fields->$key = $value; 84 | } 85 | } 86 | 87 | public function setHttpClient(\Rbs\Payment\Http\Client $client) 88 | { 89 | $this->httpClient = $client; 90 | } 91 | 92 | abstract protected function init(); 93 | abstract protected function validate(); 94 | 95 | abstract public function setCurrency($currency); 96 | abstract public function setAccountIdentifier($identifier); 97 | abstract public function setReturnOnSuccessUrl($url); 98 | abstract public function setReturnOnFailureUrl($url); 99 | abstract public function setNotificationUrl($url); 100 | abstract public function setCustomField($key, $value); 101 | abstract public function setSingleItem($name, $amount, $id = null); 102 | abstract public function setOrderNumber($orderNumber); 103 | abstract public function setTestMode($mode); 104 | abstract public function verify(); 105 | } -------------------------------------------------------------------------------- /src/tests/unit-tests/Rbs/Payment/Gateway/PaypalTest.php: -------------------------------------------------------------------------------- 1 | paypal = new \Rbs\Payment\Gateway\Paypal(); 15 | } 16 | 17 | public function testCanInitialize() 18 | { 19 | $this->assertInstanceOf("Rbs\\Payment\\Gateway\\AbstractGateway", $this->paypal); 20 | } 21 | 22 | /** 23 | * @expectedException Exception 24 | */ 25 | public function testEnsuresMinimumConfiguration() 26 | { 27 | $this->paypal->proceed(); 28 | } 29 | 30 | public function testProceedsToPaypalOnSingleItemPurchase() 31 | { 32 | $this->setupSingleItem(); 33 | 34 | ob_start(); 35 | $this->paypal->proceed(); 36 | $output = ob_get_clean(); 37 | 38 | $this->assertContains('
', $output); 39 | $this->checkDefaultMessages($output); 40 | $this->checkSingleItem($output); 41 | } 42 | 43 | public function testProceedsToPaypalSandboxOnTestMode() 44 | { 45 | $this->setupSingleItem(); 46 | $this->paypal->setTestMode(true); 47 | 48 | ob_start(); 49 | $this->paypal->proceed(); 50 | $output = ob_get_clean(); 51 | 52 | $this->assertContains('', $output); 53 | } 54 | 55 | public function testVerifiesSuccessfulTransaction() 56 | { 57 | $client = $this->setupHttpClient('VERIFIED'); 58 | $ipnData = $this->setupTestIpnData(); 59 | 60 | $this->paypal->populate($ipnData); 61 | $this->paypal->setHttpClient($client); 62 | $status = $this->paypal->verify(); 63 | 64 | $this->assertTrue($status); 65 | } 66 | 67 | public function testVerifiesInvalidTransaction() 68 | { 69 | $client = $this->setupHttpClient('INVALID'); 70 | $ipnData = $this->setupTestIpnData(); 71 | 72 | $this->paypal->populate($ipnData); 73 | $this->paypal->setHttpClient($client); 74 | $status = $this->paypal->verify(); 75 | 76 | $this->assertFalse($status); 77 | } 78 | 79 | private function setupTestIpnData() 80 | { 81 | $testIpnResponse = "mc_gross=19.95&protection_eligibility=Eligible&address_status=confirmed&payer_id=LPLWNMTBWMFAY&tax=0.00&address_street=1+Main+St&payment_date=20%3A12%3A59+Jan+13%2C+2009+PST&payment_status=Completed&charset=windows-1252&address_zip=95131&first_name=Test&mc_fee=0.88&address_country_code=US&address_name=Test+User¬ify_version=2.6&custom=&payer_status=verified&address_country=United+States&address_city=San+Jose&quantity=1&verify_sign=AtkOfCXbDm2hu0ZELryHFjY-Vb7PAUvS6nMXgysbElEn9v-1XcmSoGtf&payer_email=gpmac_1231902590_per%40paypal.com&txn_id=61E67681CH3238416&payment_type=instant&last_name=User&address_state=CA&receiver_email=gpmac_1231902686_biz%40paypal.com&payment_fee=0.88&receiver_id=S8XGHLYDW9T3S&txn_type=express_checkout&item_name=&mc_currency=USD&item_number=&residence_country=US&test_ipn=1&handling_amount=0.00&transaction_subject=&payment_gross=19.95&shipping=0.00"; 82 | $dataItems = explode("&", $testIpnResponse); 83 | $ipnData = array(); 84 | 85 | foreach ($dataItems as $item) { 86 | $values = explode("=", $item); 87 | $ipnData[$values[0]] = $values[1]; 88 | } 89 | return $ipnData; 90 | } 91 | 92 | private function setupHttpClient($status) 93 | { 94 | $client = m::mock('Rbs\Payment\Http\Client'); 95 | $client->shouldReceive('sendRequest')->andReturn($status); 96 | $client->shouldReceive('setUrl'); 97 | $client->shouldReceive('setPort'); 98 | $client->shouldReceive('setMethod'); 99 | $client->shouldReceive('setData'); 100 | return $client; 101 | } 102 | 103 | private function setupSingleItem() 104 | { 105 | $this->paypal->setAccountIdentifier('emran@rightbrainsolution.com'); 106 | $this->paypal->setCurrency('USD'); 107 | $this->paypal->setReturnOnSuccessUrl('http://phpfour.com/payment/paypal_success.php'); 108 | $this->paypal->setReturnOnFailureUrl('http://phpfour.com/payment/paypal_failure.php'); 109 | $this->paypal->setNotificationUrl('http://phpfour.com/payment/paypal_ipn.php'); 110 | $this->paypal->setSingleItem('T-shirt', 4.99, "1001"); 111 | } 112 | 113 | private function checkDefaultMessages($output) 114 | { 115 | $this->assertContains("Please wait, your order is being processed and you will be redirected to the payment website.", $output); 116 | $this->assertContains('

If you are not automatically redirected to payment website within 5 seconds,

', $output); 117 | } 118 | 119 | private function checkSingleItem($output) 120 | { 121 | $this->assertContains('', $output); 122 | $this->assertContains('', $output); 123 | $this->assertContains('', $output); 124 | $this->assertContains('', $output); 125 | $this->assertContains('', $output); 126 | $this->assertContains('', $output); 127 | $this->assertContains('', $output); 128 | $this->assertContains('', $output); 129 | $this->assertContains('', $output); 130 | $this->assertContains('', $output); 131 | } 132 | } --------------------------------------------------------------------------------