Processing HTTP notifications

Before the user sees the payment confirmation page, Yandex.Checkout sends an Order verification ( checkOrder) request to the address specified in the technical questionnaire.

The request arrives in either open or encrypted format, depending on the security measures the store is using.

You must process the request, then form a response and send it.

Below is an example of a function that processes the request and sends a response, depending on the security scheme and the current action (checkOrder/paymentAviso):

/**
 * Handles "checkOrder" and "paymentAviso" requests.
 * @param array $request payment parameters
 */
public function processRequest($request) {
    $this->log("Start " . $this->action);
    $this->log("Security type " . $this->settings->SECURITY_TYPE);
    if ($this->settings->SECURITY_TYPE == "MD5") {
        $this->log("Request: " . print_r($request, true));
        // If the MD5 checking fails, respond with "1" error code
        if (!$this->checkMD5($request)) {
            $response = $this->buildResponse($this->action, $request['invoiceId'], 1);
            $this->sendResponse($response);
        }
    } else if ($this->settings->SECURITY_TYPE == "PKCS7") {
        // Checking for a certificate sign. If the checking fails, respond with "200" error code.
        if (($request = $this->verifySign()) == null) {
            $response = $this->buildResponse($this->action, null, 200);
            $this->sendResponse($response);
        }
        $this->log("Request: " . print_r($request, true));
    }
    $response = null;
    if ($this->action == 'checkOrder') {
        $response = $this->checkOrder($request);
    } else {
        $response = $this->paymentAviso($request);
    }
    $this->sendResponse($response);
}

Checking the control amount

/**
 * Checking the MD5 sign.
 * @param  array $request payment parameters
 * @return bool true if MD5 hash is correct 
 */
private function checkMD5($request) {
    $str = $request['action'] . ";" .
        $request['orderSumAmount'] . ";" . $request['orderSumCurrencyPaycash'] . ";" .
        $request['orderSumBankPaycash'] . ";" . $request['shopId'] . ";" .
        $request['invoiceId'] . ";" . $request['customerNumber'] . ";" . $this->settings->SHOP_PASSWORD;
    $this->log("String to md5: " . $str);
    $md5 = strtoupper(md5($str));
    if ($md5 != strtoupper($request['md5'])) {
        $this->log("Wait for md5:" . $md5 . ", recieved md5: " . $request['md5']);
        return false;
    }
    return true;
}

Checking the signature

/**
 * Checking for sign when XML/PKCS#7 scheme is used.
 * @return array if request is successful, returns key-value array of request params, null otherwise.
 */
private function verifySign() {
    $descriptorspec = array(0 => array("pipe", "r"), 1 => array("pipe", "w"), 2 => array("pipe", "w"));
    $certificate = 'yamoney.pem';
    $process = proc_open('openssl smime -verify -inform PEM -nointern -certfile ' . $certificate . ' -CAfile ' . $certificate,
        $descriptorspec, $pipes);
    if (is_resource($process)) {
        // Getting data from request body.
        $data = file_get_contents($this->settings->request_source); // "php://input"
        fwrite($pipes[0], $data);
        fclose($pipes[0]);
        $content = stream_get_contents($pipes[1]);
        fclose($pipes[1]);
        $resCode = proc_close($process);
        if ($resCode != 0) {
            return null;
        } else {
            $this->log("Row xml: " . $content);
            $xml = simplexml_load_string($content);
            $array = json_decode(json_encode($xml), TRUE);
            return $array["@attributes"];
        }
    }
    return null;
}

If the security checks were passed successfully, you can perform business processing of the request.

checkOrder

/**
 * CheckOrder request processing. We suppose there are no item with price less
 * than 100 rubles in the shop.
 * @param  array $request payment parameters
 * @return string         prepared XML response
 */
private function checkOrder($request) {
    $response = null;
    if ($request['orderSumAmount'] < 100) {
        $response = $this->buildResponse($this->action, $request['invoiceId'], 100, "The amount should be more than 100 rubles.");
    } else {
        $response = $this->buildResponse($this->action, $request['invoiceId'], 0);
    }
    return $response;
}

paymentAviso

/**
 * PaymentAviso request processing.
 * @param  array $request payment parameters
 * @return string prepared response in XML format
 */
private function paymentAviso($request) {
    return $this->buildResponse($this->action, $request['invoiceId'], 0);
}

Forming the response

/**
 * Building XML response.
 * @param  string $functionName  "checkOrder" or "paymentAviso" string
 * @param  string $invoiceId     transaction number
 * @param  string $result_code   result code
 * @param  string $message       error message. May be null.
 * @return string                prepared XML response
 */
private function buildResponse($functionName, $invoiceId, $result_code, $message = null) {
    try {
        $performedDatetime = Utils::formatDate(new DateTime());
        $response = '<?xml version="1.0" encoding="UTF-8"?><' . $functionName . 'Response performedDatetime="' . $performedDatetime .
            '" code="' . $result_code . '" ' . ($message != null ? 'message="' . $message . '"' : "") . ' invoiceId="' . $invoiceId . '" shopId="' . $this->settings->SHOP_ID . '"/>';
        return $response;
    } catch (\Exception $e) {
        $this->log($e);
    }
    return null;
}

See also

HTTP-уведомления о переводах