├── .gitignore ├── Paycom ├── Application.php ├── Database.php ├── Format.php ├── Merchant.php ├── Order.php ├── PaycomException.php ├── Request.php ├── Response.php └── Transaction.php ├── README.md ├── composer.json ├── functions.php ├── index.php ├── password.paycom └── paycom.config.sample.php /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | /vendor/ 3 | paycom.config.php 4 | -------------------------------------------------------------------------------- /Paycom/Application.php: -------------------------------------------------------------------------------- 1 | merchant_id, login, keyFile keys. 17 | */ 18 | public function __construct($config) 19 | { 20 | $this->config = $config; 21 | $this->request = new Request(); 22 | $this->response = new Response($this->request); 23 | $this->merchant = new Merchant($this->config); 24 | $this->db = new Database($this->config); 25 | $this->db_conn = $this->db->new_connection(); 26 | } 27 | 28 | /** 29 | * Authorizes session and handles requests. 30 | */ 31 | public function run() 32 | { 33 | // authorize session 34 | $this->merchant->Authorize($this->request->id); 35 | 36 | // handle request 37 | try { 38 | switch ($this->request->method) { 39 | case 'CheckPerformTransaction': 40 | $this->CheckPerformTransaction(); 41 | break; 42 | case 'CheckTransaction': 43 | $this->CheckTransaction(); 44 | break; 45 | case 'CreateTransaction': 46 | $this->CreateTransaction(); 47 | break; 48 | case 'PerformTransaction': 49 | $this->PerformTransaction(); 50 | break; 51 | case 'CancelTransaction': 52 | $this->CancelTransaction(); 53 | break; 54 | case 'ChangePassword': 55 | $this->ChangePassword(); 56 | break; 57 | case 'GetStatement': 58 | $this->GetStatement(); 59 | break; 60 | default: 61 | $this->response->error( 62 | PaycomException::ERROR_METHOD_NOT_FOUND, 63 | 'Method not found.', 64 | $this->request->method 65 | ); 66 | break; 67 | } 68 | } catch (PaycomException $exc) { 69 | $exc->send(); 70 | } 71 | } 72 | 73 | private function CheckPerformTransaction() 74 | { 75 | $order = new Order($this->request->id, $this->request->params); 76 | $order->find($this->request->params['account']); 77 | 78 | // validate parameters 79 | $order->validate($this->request->params); 80 | 81 | // todo: Check is there another active or completed transaction for this order 82 | $transaction = new Transaction($this->db_conn); 83 | $found = $transaction->find($this->request->params); 84 | if ($found && ($found->state == Transaction::STATE_CREATED || $found->state == Transaction::STATE_COMPLETED)) { 85 | $this->response->error( 86 | PaycomException::ERROR_COULD_NOT_PERFORM, 87 | 'There is other active/completed transaction for this order.' 88 | ); 89 | } 90 | 91 | // if control is here, then we pass all validations and checks 92 | // send response, that order is ready to be paid. 93 | $this->response->send(['allow' => true]); 94 | } 95 | 96 | private function CheckTransaction() 97 | { 98 | // todo: Find transaction by id 99 | $transaction = new Transaction($this->db_conn); 100 | $found = $transaction->find($this->request->params); 101 | if (!$found) { 102 | $this->response->error( 103 | PaycomException::ERROR_TRANSACTION_NOT_FOUND, 104 | 'Transaction not found.' 105 | ); 106 | } 107 | 108 | // todo: Prepare and send found transaction 109 | $this->response->send([ 110 | 'create_time' => Format::datetime2timestamp($found->create_time), 111 | 'perform_time' => Format::datetime2timestamp($found->perform_time), 112 | 'cancel_time' => Format::datetime2timestamp($found->cancel_time), 113 | 'transaction' => $found->id, 114 | 'state' => $found->state, 115 | 'reason' => isset($found->reason) ? 1 * $found->reason : null 116 | ]); 117 | } 118 | 119 | private function CreateTransaction() 120 | { 121 | $order = new Order($this->request->id, $this->request->params); 122 | $order->find($this->request->params['account']); 123 | 124 | // validate parameters 125 | $order->validate($this->request->params); 126 | 127 | // todo: Find transaction by id 128 | $transaction = new Transaction($this->db_conn); 129 | $found = $transaction->find($this->request->params); 130 | 131 | if ($found) { 132 | if ($found->state != Transaction::STATE_CREATED) { // validate transaction state 133 | $this->response->error( 134 | PaycomException::ERROR_COULD_NOT_PERFORM, 135 | 'Transaction found, but is not active.' 136 | ); 137 | } elseif ($found->isExpired()) { // if transaction timed out, cancel it and send error 138 | $found->cancel(Transaction::REASON_CANCELLED_BY_TIMEOUT); 139 | $this->response->error( 140 | PaycomException::ERROR_COULD_NOT_PERFORM, 141 | 'Transaction is expired.' 142 | ); 143 | } else { // if transaction found and active, send it as response 144 | $this->response->send([ 145 | 'create_time' => Format::datetime2timestamp($found->create_time), 146 | 'transaction' => $found->id, 147 | 'state' => $found->state, 148 | 'receivers' => $found->receivers 149 | ]); 150 | } 151 | } else { // transaction not found, create new one 152 | 153 | // validate new transaction time 154 | if (Format::timestamp2milliseconds(1 * $this->request->params['time']) - Format::timestamp(true) >= Transaction::TIMEOUT) { 155 | $this->response->error( 156 | PaycomException::ERROR_INVALID_ACCOUNT, 157 | PaycomException::message( 158 | 'С даты создания транзакции прошло ' . Transaction::TIMEOUT . 'мс', 159 | 'Tranzaksiya yaratilgan sanadan ' . Transaction::TIMEOUT . 'ms o`tgan', 160 | 'Since create time of the transaction passed ' . Transaction::TIMEOUT . 'ms' 161 | ), 162 | 'time' 163 | ); 164 | } 165 | 166 | // create new transaction 167 | // keep create_time as timestamp, it is necessary in response 168 | $create_time = Format::timestamp(); 169 | $transaction->paycom_transaction_id = $this->request->params['id']; 170 | $transaction->paycom_time = $this->request->params['time']; 171 | $transaction->paycom_time_datetime = Format::timestamp2datetime($this->request->params['time']); 172 | $transaction->create_time = Format::timestamp2datetime($create_time); 173 | $transaction->state = Transaction::STATE_CREATED; 174 | $transaction->amount = $this->request->amount; 175 | $transaction->order_id = $this->request->account('order_id'); 176 | $transaction->save(); // after save $transaction->id will be populated with the newly created transaction's id. 177 | 178 | // send response 179 | $this->response->send([ 180 | 'create_time' => $create_time, 181 | 'transaction' => $transaction->id, 182 | 'state' => $transaction->state, 183 | 'receivers' => null 184 | ]); 185 | } 186 | } 187 | 188 | private function PerformTransaction() 189 | { 190 | $transaction = new Transaction($this->db_conn); 191 | // search transaction by id 192 | $found = $transaction->find($this->request->params); 193 | 194 | // if transaction not found, send error 195 | if (!$found) { 196 | $this->response->error(PaycomException::ERROR_TRANSACTION_NOT_FOUND, 'Transaction not found.'); 197 | } 198 | 199 | switch ($found->state) { 200 | case Transaction::STATE_CREATED: // handle active transaction 201 | if ($found->isExpired()) { // if transaction is expired, then cancel it and send error 202 | $found->cancel(Transaction::REASON_CANCELLED_BY_TIMEOUT); 203 | $this->response->error( 204 | PaycomException::ERROR_COULD_NOT_PERFORM, 205 | 'Transaction is expired.' 206 | ); 207 | } else { // perform active transaction 208 | // todo: Mark order/service as completed 209 | $params = ['order_id' => $found->order_id]; 210 | $order = new Order($this->request->id); 211 | $order->find($params); 212 | $order->changeState(Order::STATE_PAY_ACCEPTED); 213 | 214 | // todo: Mark transaction as completed 215 | $perform_time = Format::timestamp(); 216 | $found->state = Transaction::STATE_COMPLETED; 217 | $found->perform_time = Format::timestamp2datetime($perform_time); 218 | $found->save(); 219 | 220 | $this->response->send([ 221 | 'transaction' => $found->id, 222 | 'perform_time' => $perform_time, 223 | 'state' => $found->state 224 | ]); 225 | } 226 | break; 227 | 228 | case Transaction::STATE_COMPLETED: // handle complete transaction 229 | // todo: If transaction completed, just return it 230 | $this->response->send([ 231 | 'transaction' => $found->id, 232 | 'perform_time' => Format::datetime2timestamp($found->perform_time), 233 | 'state' => $found->state 234 | ]); 235 | break; 236 | 237 | default: 238 | // unknown situation 239 | $this->response->error( 240 | PaycomException::ERROR_COULD_NOT_PERFORM, 241 | 'Could not perform this operation.' 242 | ); 243 | break; 244 | } 245 | } 246 | 247 | private function CancelTransaction() 248 | { 249 | $transaction = new Transaction($this->db_conn); 250 | 251 | // search transaction by id 252 | $found = $transaction->find($this->request->params); 253 | 254 | // if transaction not found, send error 255 | if (!$found) { 256 | $this->response->error(PaycomException::ERROR_TRANSACTION_NOT_FOUND, 'Transaction not found.'); 257 | } 258 | 259 | switch ($found->state) { 260 | // if already cancelled, just send it 261 | case Transaction::STATE_CANCELLED: 262 | case Transaction::STATE_CANCELLED_AFTER_COMPLETE: 263 | $this->response->send([ 264 | 'transaction' => $found->id, 265 | 'cancel_time' => Format::datetime2timestamp($found->cancel_time), 266 | 'state' => $found->state 267 | ]); 268 | break; 269 | 270 | // cancel active transaction 271 | case Transaction::STATE_CREATED: 272 | // cancel transaction with given reason 273 | $found->cancel(1 * $this->request->params['reason']); 274 | // after $found->cancel(), cancel_time and state properties populated with data 275 | 276 | // change order state to cancelled 277 | $order = new Order($this->request->id); 278 | $order->find($this->request->params); 279 | $order->changeState(Order::STATE_CANCELLED); 280 | 281 | // send response 282 | $this->response->send([ 283 | 'transaction' => $found->id, 284 | 'cancel_time' => Format::datetime2timestamp($found->cancel_time), 285 | 'state' => $found->state 286 | ]); 287 | break; 288 | 289 | case Transaction::STATE_COMPLETED: 290 | // find order and check, whether cancelling is possible this order 291 | $order = new Order($this->request->id); 292 | $order->find($this->request->params); 293 | if ($order->allowCancel()) { 294 | // cancel and change state to cancelled 295 | $found->cancel(1 * $this->request->params['reason']); 296 | // after $found->cancel(), cancel_time and state properties populated with data 297 | 298 | $order->changeState(Order::STATE_CANCELLED); 299 | 300 | // send response 301 | $this->response->send([ 302 | 'transaction' => $found->id, 303 | 'cancel_time' => Format::datetime2timestamp($found->cancel_time), 304 | 'state' => $found->state 305 | ]); 306 | } else { 307 | // todo: If cancelling after performing transaction is not possible, then return error -31007 308 | $this->response->error( 309 | PaycomException::ERROR_COULD_NOT_CANCEL, 310 | 'Could not cancel transaction. Order is delivered/Service is completed.' 311 | ); 312 | } 313 | break; 314 | } 315 | } 316 | 317 | private function ChangePassword() 318 | { 319 | // validate, password is specified, otherwise send error 320 | if (!isset($this->request->params['password']) || !trim($this->request->params['password'])) { 321 | $this->response->error(PaycomException::ERROR_INVALID_ACCOUNT, 'New password not specified.', 'password'); 322 | } 323 | 324 | // if current password specified as new, then send error 325 | if ($this->merchant->config['key'] == $this->request->params['password']) { 326 | $this->response->error(PaycomException::ERROR_INSUFFICIENT_PRIVILEGE, 'Insufficient privilege. Incorrect new password.'); 327 | } 328 | 329 | // todo: Implement saving password into data store or file 330 | // example implementation, that saves new password into file specified in the configuration 331 | if (!file_put_contents($this->config['keyFile'], $this->request->params['password'])) { 332 | $this->response->error(PaycomException::ERROR_INTERNAL_SYSTEM, 'Internal System Error.'); 333 | } 334 | 335 | // if control is here, then password is saved into data store 336 | // send success response 337 | $this->response->send(['success' => true]); 338 | } 339 | 340 | private function GetStatement() 341 | { 342 | // validate 'from' 343 | if (!isset($this->request->params['from'])) { 344 | $this->response->error(PaycomException::ERROR_INVALID_ACCOUNT, 'Incorrect period.', 'from'); 345 | } 346 | 347 | // validate 'to' 348 | if (!isset($this->request->params['to'])) { 349 | $this->response->error(PaycomException::ERROR_INVALID_ACCOUNT, 'Incorrect period.', 'to'); 350 | } 351 | 352 | // validate period 353 | if (1 * $this->request->params['from'] >= 1 * $this->request->params['to']) { 354 | $this->response->error(PaycomException::ERROR_INVALID_ACCOUNT, 'Incorrect period. (from >= to)', 'from'); 355 | } 356 | 357 | // get list of transactions for specified period 358 | $transaction = new Transaction($this->db_conn); 359 | $transactions = $transaction->report($this->request->params['from'], $this->request->params['to']); 360 | 361 | // send results back 362 | $this->response->send(['transactions' => $transactions]); 363 | } 364 | } -------------------------------------------------------------------------------- /Paycom/Database.php: -------------------------------------------------------------------------------- 1 | config = $config; 12 | } 13 | 14 | public function new_connection() 15 | { 16 | $db = null; 17 | 18 | // connect to the database 19 | $db_options = [\PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC]; 20 | 21 | $db = new \PDO( 22 | 'mysql:dbname=' . $this->config['db']['database'] . ';host=localhost;charset=utf8', 23 | $this->config['db']['username'], 24 | $this->config['db']['password'], 25 | $db_options 26 | ); 27 | 28 | return $db; 29 | } 30 | } -------------------------------------------------------------------------------- /Paycom/Format.php: -------------------------------------------------------------------------------- 1 | config = $config; 11 | 12 | // read key from key file 13 | if ($this->config['keyFile']) { 14 | $this->config['key'] = file_get_contents($this->config['keyFile']); 15 | } 16 | } 17 | 18 | public function Authorize($request_id) 19 | { 20 | $headers = getallheaders(); 21 | 22 | if (!$headers || !isset($headers['Authorization']) || 23 | !preg_match('/^\s*Basic\s+(\S+)\s*$/i', $headers['Authorization'], $matches) || 24 | base64_decode($matches[1]) != $this->config['login'] . ":" . $this->config['key'] 25 | ) { 26 | throw new PaycomException( 27 | $request_id, 28 | 'Insufficient privilege to perform this method.', 29 | PaycomException::ERROR_INSUFFICIENT_PRIVILEGE 30 | ); 31 | } 32 | 33 | return true; 34 | } 35 | } -------------------------------------------------------------------------------- /Paycom/Order.php: -------------------------------------------------------------------------------- 1 | request_id = $request_id; 24 | } 25 | 26 | /** 27 | * Validates amount and account values. 28 | * @param array $params amount and account parameters to validate. 29 | * @return bool true - if validation passes 30 | * @throws PaycomException - if validation fails 31 | */ 32 | public function validate(array $params) 33 | { 34 | // todo: Validate amount, if failed throw error 35 | // for example, check amount is numeric 36 | if (!is_numeric($params['amount'])) { 37 | throw new PaycomException( 38 | $this->request_id, 39 | 'Incorrect amount.', 40 | PaycomException::ERROR_INVALID_AMOUNT 41 | ); 42 | } 43 | 44 | // todo: Validate account, if failed throw error 45 | // assume, we should have order_id 46 | if (!isset($params['account']['order_id'])) { 47 | throw new PaycomException( 48 | $this->request_id, 49 | PaycomException::message( 50 | 'Неверный код заказа.', 51 | 'Harid kodida xatolik.', 52 | 'Incorrect order code.' 53 | ), 54 | PaycomException::ERROR_INVALID_ACCOUNT, 55 | 'order_id' 56 | ); 57 | } 58 | 59 | // todo: Check is order available 60 | 61 | // assume, after find() $this will be populated with Order data 62 | $this->find($params['account']['order_id']); 63 | 64 | // for example, order state before payment should be 'waiting pay' 65 | if ($this->state != self::STATE_WAITING_PAY) { 66 | throw new PaycomException( 67 | $this->request_id, 68 | 'Order state is invalid.', 69 | PaycomException::ERROR_COULD_NOT_PERFORM 70 | ); 71 | } 72 | 73 | // keep params for further use 74 | $this->params = $params; 75 | 76 | return true; 77 | } 78 | 79 | /** 80 | * Find order by given parameters. 81 | * @param mixed $params parameters. 82 | * @return Order|Order[] found order or array of orders. 83 | */ 84 | public function find($params) 85 | { 86 | // todo: Implement searching order(s) by given parameters, populate current instance with data 87 | } 88 | 89 | /** 90 | * Change order's state to specified one. 91 | * @param int $state new state of the order 92 | * @return void 93 | */ 94 | public function changeState($state) 95 | { 96 | // todo: Implement changing order state (reserve order after create transaction or free order after cancel) 97 | } 98 | 99 | /** 100 | * Check, whether order can be cancelled or not. 101 | * @return bool true - order is cancellable, otherwise false. 102 | */ 103 | public function allowCancel() 104 | { 105 | // todo: Implement order cancelling allowance check 106 | } 107 | } -------------------------------------------------------------------------------- /Paycom/PaycomException.php: -------------------------------------------------------------------------------- 1 | request_id = $request_id; 30 | $this->message = $message; 31 | $this->code = $code; 32 | $this->data = $data; 33 | 34 | // prepare error data 35 | $this->error = ['code' => $this->code]; 36 | 37 | if ($this->message) { 38 | $this->error['message'] = $this->message; 39 | } 40 | 41 | if ($this->data) { 42 | $this->error['data'] = $this->data; 43 | } 44 | } 45 | 46 | public function send() 47 | { 48 | header('Content-Type: application/json; charset=UTF-8'); 49 | 50 | // create response 51 | $response['id'] = $this->request_id; 52 | $response['result'] = null; 53 | $response['error'] = $this->error; 54 | 55 | echo json_encode($response); 56 | } 57 | 58 | public static function message($ru, $uz = '', $en = '') 59 | { 60 | return ['ru' => $ru, 'uz' => $uz, 'en' => $en]; 61 | } 62 | } -------------------------------------------------------------------------------- /Paycom/Request.php: -------------------------------------------------------------------------------- 1 | CreateTransaction */ 13 | public $method; 14 | 15 | /** @var array request parameters, such as amount, account */ 16 | public $params; 17 | 18 | /** @var int amount value in coins */ 19 | public $amount; 20 | 21 | /** 22 | * Request constructor. 23 | * Parses request payload and populates properties with values. 24 | */ 25 | public function __construct() 26 | { 27 | $request_body = file_get_contents('php://input'); 28 | $this->payload = json_decode($request_body, true); 29 | 30 | if (!$this->payload) { 31 | throw new PaycomException( 32 | null, 33 | 'Invalid JSON-RPC object.', 34 | PaycomException::ERROR_INVALID_JSON_RPC_OBJECT 35 | ); 36 | } 37 | 38 | // populate request object with data 39 | $this->id = isset($this->payload['id']) ? 1 * $this->payload['id'] : null; 40 | $this->method = isset($this->payload['method']) ? trim($this->payload['method']) : null; 41 | $this->params = isset($this->payload['params']) ? $this->payload['params'] : []; 42 | $this->amount = isset($this->payload['params']['amount']) ? 1 * $this->payload['params']['amount'] : null; 43 | 44 | // add request id into params too 45 | $this->params['request_id'] = $this->id; 46 | } 47 | 48 | /** 49 | * Gets account parameter if such exists, otherwise returns null. 50 | * @param string $param name of the parameter. 51 | * @return mixed|null account parameter value or null if such parameter doesn't exists. 52 | */ 53 | public function account($param) 54 | { 55 | return isset($this->params['account'], $this->params['account'][$param]) ? $this->params['account'][$param] : null; 56 | } 57 | } -------------------------------------------------------------------------------- /Paycom/Response.php: -------------------------------------------------------------------------------- 1 | request = $request; 13 | } 14 | 15 | /** 16 | * Sends response with the given result and error. 17 | * @param mixed $result result of the request. 18 | * @param mixed|null $error error. 19 | */ 20 | public function send($result, $error = null) 21 | { 22 | header('Content-Type: application/json; charset=UTF-8'); 23 | 24 | $response['id'] = $this->request->id; 25 | $response['result'] = $result; 26 | $response['error'] = $error; 27 | 28 | echo json_encode($response); 29 | } 30 | 31 | /** 32 | * Generates PaycomException exception with given parameters. 33 | * @param int $code error code. 34 | * @param string|array $message error message. 35 | * @param string $data parameter name, that resulted to this error. 36 | * @throws PaycomException 37 | */ 38 | public function error($code, $message = null, $data = null) 39 | { 40 | throw new PaycomException($this->request->id, $message, $code, $data); 41 | } 42 | } -------------------------------------------------------------------------------- /Paycom/Transaction.php: -------------------------------------------------------------------------------- 1 | db_conn = $db_conn; 92 | } 93 | 94 | /** 95 | * Saves current transaction instance in a data store. 96 | * @return void 97 | */ 98 | public function save() 99 | { 100 | // todo: Implement creating/updating transaction into data store 101 | // todo: Populate $id property with newly created transaction id 102 | } 103 | 104 | /** 105 | * Cancels transaction with the specified reason. 106 | * @param int $reason cancelling reason. 107 | * @return void 108 | */ 109 | public function cancel($reason) 110 | { 111 | // todo: Implement transaction cancelling on data store 112 | 113 | // todo: Populate $cancel_time with value 114 | $this->cancel_time = Format::timestamp2datetime(Format::timestamp()); 115 | 116 | // todo: Change $state to cancelled (-1 or -2) according to the current state 117 | // Scenario: CreateTransaction -> CancelTransaction 118 | $this->state = self::STATE_CANCELLED; 119 | // Scenario: CreateTransaction -> PerformTransaction -> CancelTransaction 120 | if ($this->state == self::STATE_COMPLETED) { 121 | $this->state = self::STATE_CANCELLED_AFTER_COMPLETE; 122 | } 123 | 124 | // set reason 125 | $this->reason = $reason; 126 | 127 | // todo: Update transaction on data store 128 | } 129 | 130 | /** 131 | * Determines whether current transaction is expired or not. 132 | * @return bool true - if current instance of the transaction is expired, false - otherwise. 133 | */ 134 | public function isExpired() 135 | { 136 | // todo: Implement transaction expiration check 137 | // for example, if transaction is active and passed TIMEOUT milliseconds after its creation, then it is expired 138 | return $this->state == self::STATE_CREATED && Format::datetime2timestamp($this->create_time) - time() > self::TIMEOUT; 139 | } 140 | 141 | /** 142 | * Find transaction by given parameters. 143 | * @param mixed $params parameters 144 | * @return Transaction|Transaction[] 145 | */ 146 | public function find($params) 147 | { 148 | $is_success = false; 149 | $db_stmt = null; 150 | 151 | // todo: Implement searching transaction by id, populate current instance with data and return it 152 | if (isset($params['id'])) { 153 | $sql = 'select * from transactions where paycom_transaction_id=:trx_id'; 154 | $db_stmt = $this->db_conn->prepare($sql); 155 | $is_success = $db_stmt->execute([':trx_id' => $params['id']]); 156 | } 157 | 158 | // todo: Implement searching transactions by given parameters and return list of transactions 159 | 160 | // if SQL operation succeeded, then try to populate instance properties with values 161 | if ($is_success) { 162 | 163 | // fetch one row 164 | $row = $db_stmt->fetch(); 165 | 166 | // if there is row available, then populate properties with values 167 | if ($row) { 168 | 169 | $this->id = $row['id']; 170 | $this->paycom_transaction_id = $row['paycom_transaction_id']; 171 | $this->paycom_time = 1 * $row['paycom_time']; 172 | $this->paycom_time_datetime = $row['paycom_time_datetime']; 173 | $this->create_time = $row['create_time']; 174 | $this->perform_time = $row['perform_time']; 175 | $this->cancel_time = $row['cancel_time']; 176 | $this->state = 1 * $row['state']; 177 | $this->reason = $row['reason'] ? 1 * $row['reason'] : null; 178 | $this->amount = 1 * $row['amount']; 179 | $this->order_id = 1 * $row['order_id']; 180 | 181 | // assume, receivers column contains list of receivers in JSON format as string 182 | $this->receivers = $row['receivers'] ? json_decode($row['receivers'], true) : null; 183 | 184 | // return populated instance 185 | return $this; 186 | } 187 | 188 | } 189 | 190 | // transaction not found, return null 191 | return null; 192 | 193 | // Possible features: 194 | // Search transaction by product/order id that specified in $params 195 | // Search transactions for a given period of time that specified in $params 196 | } 197 | 198 | /** 199 | * Gets list of transactions for the given period including period boundaries. 200 | * @param int $from_date start of the period in timestamp. 201 | * @param int $to_date end of the period in timestamp. 202 | * @return array list of found transactions converted into report format for send as a response. 203 | */ 204 | public function report($from_date, $to_date) 205 | { 206 | $from_date = Format::timestamp2datetime($from_date); 207 | $to_date = Format::timestamp2datetime($to_date); 208 | 209 | // container to hold rows/document from data store 210 | $rows = []; 211 | 212 | // todo: Retrieve transactions for the specified period from data store 213 | 214 | // assume, here we have $rows variable that is populated with transactions from data store 215 | // normalize data for response 216 | $result = []; 217 | foreach ($rows as $row) { 218 | $result[] = [ 219 | 'id' => $row['paycom_transaction_id'], // paycom transaction id 220 | 'time' => 1 * $row['paycom_time'], // paycom transaction timestamp as is 221 | 'amount' => 1 * $row['amount'], 222 | 'account' => [ 223 | 'order_id' => $row['order_id'], // account parameters to identify client/order/service 224 | // ... additional parameters may be listed here, which are belongs to the account 225 | ], 226 | 'create_time' => Format::datetime2timestamp($row['create_time']), 227 | 'perform_time' => Format::datetime2timestamp($row['perform_time']), 228 | 'cancel_time' => Format::datetime2timestamp($row['cancel_time']), 229 | 'transaction' => $row['id'], 230 | 'state' => 1 * $row['state'], 231 | 'reason' => isset($row['reason']) ? 1 * $row['reason'] : null, 232 | 'receivers' => $row['receivers'] 233 | ]; 234 | } 235 | 236 | return $result; 237 | } 238 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NOTE 2 | 3 | Please, use the following up to date repo instead https://github.com/PaycomUZ/paycom-integration-php-template 4 | 5 | # Paycom integration template 6 | 7 | ## Prerequisites 8 | 9 | - `PHP 5.4` or greater 10 | - [`PDO`](http://php.net/manual/en/book.pdo.php) extension 11 | - [`Composer`](https://getcomposer.org/download/) dependency manager 12 | 13 | ## Installation via Composer 14 | 15 | Change current directory to your project folder and install package: 16 | ``` 17 | cd my-shop-project 18 | composer create-project paycom/integration-template 19 | ``` 20 | 21 | ## Installation via Git 22 | ``` 23 | git clone https://github.com/umidjons/paycom-integration-php-template.git 24 | cd paycom-integration-php-template 25 | composer dumpautoload 26 | ``` 27 | 28 | From now you can use classes from package as following: 29 | ```php 30 | run(); 41 | ``` 42 | 43 | Make copy of `paycom.config.sample.php` as `paycom.config.php` and set your real settings there. 44 | 45 | Assuming your domain as `https://myshop.uz`, 46 | now you can set entry point URL to handle requests from Paycom as `https://myshop.uz/api/index.php`. 47 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "paycom/integration-template", 3 | "description": "Set of classes to easy integration with Paycom (Payme) payment system", 4 | "type": "library", 5 | "keywords": [ 6 | "paycom", 7 | "payme" 8 | ], 9 | "authors": [ 10 | { 11 | "name": "Umidjons", 12 | "email": "almatov.us@gmail.com" 13 | } 14 | ], 15 | "autoload": { 16 | "psr-4": { 17 | "Paycom\\": "Paycom/" 18 | } 19 | }, 20 | "require": { 21 | "php": ">=5.4.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /functions.php: -------------------------------------------------------------------------------- 1 | $value) { 9 | if (substr($name, 0, 5) == 'HTTP_') { 10 | $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value; 11 | } 12 | } 13 | return $headers; 14 | } 15 | } -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | run(); 16 | -------------------------------------------------------------------------------- /password.paycom: -------------------------------------------------------------------------------- 1 | Replace this line with your KEY, that is obtained from the merchant's cabinet -------------------------------------------------------------------------------- /paycom.config.sample.php: -------------------------------------------------------------------------------- 1 | '69240ea9058e46ea7a1b806a', 5 | 'login' => 'Paycom', 6 | 'keyFile' => 'password.paycom', 7 | 'db' => [ 8 | 'database' => '', 9 | 'username' => '', 10 | 'password' => '' 11 | ], 12 | ]; --------------------------------------------------------------------------------