As the name suggests, face-to-face payment helps merchants achieve quick collection in offline consumption scenarios; face-to-face payment products support two payment methods: barcode payment and scan code payment.
What we connect here is scan code payment. Scan code payment refers to a mode in which users open the “scan” function in Alipay wallet, scan the QR code displayed by the merchant in a certain cashier scene, and make payment. This model is suitable for offline physical store payment, face-to-face payment and other scenarios. The business process is shown in the figure below: Since signing a contract with face-to-face payment is very simple, individual industrial and commercial households/individual merchants are allowed to sign a contract. Therefore, this method is also widely used for online QR code payment. Since this method violates the relevant terms of Alipay, it has certain risks. As a technical exchange, we will put this issue aside for the time being.

As a technical docking, even if you do not sign a contract and pay for the product in person, you can still develop it.
Payment ability directly involves transactions and funds. In order to facilitate developers to debug payment ability, the open platform has prepared a sandbox environment, including a sandbox environment account and a sandbox version of Alipay wallet, so that developers can debug in the sandbox environment. Click to learn How to connect to the sandbox and Connect to the sandbox environment.
So I use a sandbox environment for development here. After all, there is a lot of money in it, so you can use it as you like.
First download the SDK of the corresponding development language. Download: https://docs.open.alipay.com/194/105201/ Scan code payment document: https://docs.open.alipay.com/194/106078/

Configuration Key In order to ensure the identity and data security of both parties to the transaction (merchant and Alipay), developers need to configure the keys of both parties and verify the transaction data before calling the interface.
Download Alipay Open Platform Development Assistant to generate the key.
After generating the key, developers need to configure the key in the open platform developer center. After the configuration is completed, the Alipay public key can be obtained Design Access Since my design here does not need to use polling (will be discussed later), I did not add it. Below is the relevant code from my business

public function pay(){

    if (request()->isPost()) {
        
        // (Required) The unique order number in the merchant website order system, within 64 characters, can only contain letters, numbers, and underscores.
        // It is necessary to ensure that the merchant system cannot be repeated. It is recommended to generate it through the database sequence.
        $uid = Session::get('sq.uid');
        $outTradeNo = order\_num() . $uid;

        // (Required) Order title, roughly describing the user's payment purpose. For example, "pay in person at xxx brand xxx store and scan the QR code to consume"
        $subject = 'Aggregation platform user points recharge';

        // (Required) The total amount of the order, in yuan, cannot exceed 100 million yuan
        // If [discount amount], [non-discountable amount], and [total order amount] are passed in at the same time, the following conditions must be met: [total order amount] = [discount amount] + [non-discountable amount]
        $totalAmount = input('post.pay\_money/f');
        if($totalAmount < 1){
            return \['status' => 1, 'msg' => 'Minimum recharge amount 1 yuan'\];
        }
        if($totalAmount > 9999999){
            return \['status' => 1, 'msg' => 'The maximum recharge amount cannot exceed 9999999 yuan'\];
        }


        // (Not recommended) The amount of the order that can be discounted. Discount activities can be configured in conjunction with the merchant platform. If some of the products in the order are discounted, the total price of some of the products can be filled in this field. By default, all products can be discounted.
        // If this value is not passed in, but [total order amount] and [non-discountable amount] are passed in, the value defaults to [total order amount] - [non-discountable amount]
        //String discountableAmount = "1.00"; //

        // (Optional) The amount of the order cannot be discounted. Discount activities can be configured in conjunction with the merchant platform. If drinks are not included in the discount, fill in the corresponding amount in this field.
        // If this value is not passed in, but [Total Order Amount] and [Discount Amount] are passed in, then the value defaults to [Total Order Amount] - [Discount Amount]
        // $undiscountableAmount = "0.01";

        // The seller's Alipay account ID is used to support payments to different collection accounts under one contract account (payments are made to the Alipay account corresponding to the sellerId)
        // If this field is empty, it defaults to the PID of the merchant contracted with Alipay, which is the PID corresponding to the appid.
        //$sellerId = "";

        // Order description, you can give a detailed description of the transaction or product, for example, fill in "Purchase 2 items for a total of 15.00 yuan"
        $body = "Aggregation platform user points recharge" . $totalAmount . 'yuan';

        //Merchant operator number, add this parameter to make sales statistics for the merchant operator
        // $operatorId = "";

        // (Optional) Merchant store number. Discount information accurate to the store can be configured through the store number and merchant backend. Please contact Alipay technical support for details.
        // $storeId = "";

        // Alipay store number
        // $alipayStoreId= "";

        //Business expansion parameters, currently you can add the system provider number assigned by Alipay (through the setSysServiceProviderId method), which can be developed and used by the system provider. For details, please consult Alipay technical support
        // $providerId = ""; //System provider pid, used as the basis for extracting system provider rebate data
        // $extendParams = new ExtendParams();
        // $extendParams->setSysServiceProviderId($providerId);
        // $extendParamsArr = $extendParams->getExtendParams();

        // Payment timeout, offline code scanning transactions are defined as 5 minutes
        $timeExpress = "5m";

        // Product details list, you need to fill in the purchase product details.
        // $goodsDetailList = array();

        // // Create a product information. The parameter meanings are product id (use national standard), name, unit price (unit: cents), and quantity. If you need to add a product category, see GoodsDetail for details.
        // $goods1 = new GoodsDetail();
        // $goods1->setGoodsId("apple-01");
        // $goods1->setGoodsName("iphone");
        // $goods1->setPrice(3000);
        // $goods1->setQuantity(1);
        // //Get product 1 details array
        // $goods1Arr = $goods1->getGoodsDetail();

        // // Continue to create and add the first product information. The product purchased by the user is "xx toothbrush", the unit price is 5.05 yuan, and two pieces are purchased
        // $goods2 = new GoodsDetail();
        // $goods2->setGoodsId("apple-02");
        // $goods2->setGoodsName("ipad");
        // $goods2->setPrice(1000);
        // $goods2->setQuantity(1);
        // //Get product 1 details array
        // $goods2Arr = $goods2->getGoodsDetail();

        // $goodsDetailList = array($goods1Arr,$goods2Arr);

        //Third-party application authorization token, used in merchant authorization system developer development mode
        $appAuthToken = "";//Fill in based on the real value

        //Create request builder and set request parameters
        $qrPayRequestBuilder = new AlipayTradePrecreateContentBuilder();
        $qrPayRequestBuilder->setOutTradeNo($outTradeNo);
        $qrPayRequestBuilder->setTotalAmount($totalAmount);
        $qrPayRequestBuilder->setTimeExpress($timeExpress);
        $qrPayRequestBuilder->setSubject($subject);
        $qrPayRequestBuilder->setBody($body);
        // $qrPayRequestBuilder->setUndiscountableAmount($undiscountableAmount);
        // $qrPayRequestBuilder->setExtendParams($extendParamsArr);
        // $qrPayRequestBuilder->setGoodsDetailList($goodsDetailList);
        // $qrPayRequestBuilder->setStoreId($storeId);
        // $qrPayRequestBuilder->setOperatorId($operatorId);
        // $qrPayRequestBuilder->setAlipayStoreId($alipayStoreId);

        $qrPayRequestBuilder->setAppAuthToken($appAuthToken);


        // Call the qrPay method to obtain the in-person payment response
        require ROOT\_PATH.'extend/f2fpay/config/config.php';
        $qrPay = new AlipayTradeService($config);
        $qrPayResult = $qrPay->qrPay($qrPayRequestBuilder);

        //Perform business processing based on status value
        switch ($qrPayResult->getTradeStatus()){
            case "SUCCESS":
                $response = $qrPayResult->getResponse();

                Db::name('order')
                    ->insert(\[
                        'uid' => $uid,
                        'pay\_id' => $outTradeNo,
                        'money' => $totalAmount,
                        'creat\_time' => time(),
                        'subject' => $subject
                    \]);

                return \['status' => 0, 'msg' => 'Alipay created order QR code successfully!!!"','data' => \[
                    'qr\_code' => $response->qr\_code,
                    'outTradeNo' => $outTradeNo
                \]\];
                // $qrcode = $qrPay->create\_erweima($response->qr\_code);
                // echo $qrcode;
                // print\_r($response);
                break;
            case "FAILED":
                return \['status' => 1, 'msg' => 'Alipay failed to create order QR code!!!"'\];
                // if(!empty($qrPayResult->getResponse())){
                // print\_r($qrPayResult->getResponse());
                // }
                break;
            case "UNKNOWN":
                return \['status' => 1, 'msg' => 'System abnormality, status unknown!!!"'\];
                // echo "System abnormality, status unknown!!!"."<br>--------------------------<br>";
                // if(!empty($qrPayResult->getResponse())){
                // print\_r($qrPayResult->getResponse());
                // }
                break;
            default:
                return \['status' => 1, 'msg' => 'Unsupported return status, creating order QR code returns exception!!!'\];
                break;
        }
        return ;
    }


}

The above is the pre-order code for face-to-face payment Regarding this SDK, it is very necessary for me to complain about who wrote the demo and introduced a lotusphp framework into the PHP example. It is a lot of useless things and has no consideration at all whether we developers can accept it.
I also spent some time to streamline the SDK and only took out the parts I needed, put them into my own framework, added namespace, and automatically loaded them.
Scan code payment has a unique function - asynchronous notification This is also the most needed function for online payment. When the cashier calls the pre-order request API to generate a QR code and displays it to the user, the user scans the QR code with their mobile phone to pay. Alipay will notify the merchant system of the change information of the order along with the asynchronous notification address notify_url passed in when the merchant calls the pre-order request, and notify the merchant system of the payment result as a parameter in the form of a POST request.
Remember that this asynchronous notification address needs to be set in the application.

// Asynchronous callback
public function notify() {
    if (request()->isPost()) {
        require ROOT\_PATH.'extend/f2fpay/config/config.php';
        $aop = new AopClient;
        $aop->alipayrsaPublicKey = $config\['alipay\_public\_key'\];
        $flag = $aop->rsaCheckV1($\_POST, null, "RSA2");
        if ($flag) {
            //The asynchronous SIGN verification is successful and the next step can be taken. For example, verify the order amount and then complete the order. Something like that. .
            //What needs to be verified is whether the order number and the order amount are consistent. If the verification is successful, the order in the database can be operated.
            //TRADE\_SUCCESS For in-person payments, the payment has already been received. Details can be found here https://www.cnblogs.com/tdalcn/p/5956690.html
            if ($\_POST\['trade\_status'\] === "TRADE\_SUCCESS") {
                //Order processing template
                
                $res = Db::name('order')
                    ->where('pay\_id', $\_POST\['out\_trade\_no'\])
                    ->where('money', $\_POST\['total\_amount'\])
                    ->where('status', 0)
                    ->find();
                if($res){
                    Db::name('order')
                        ->where('id',$res\['id'\])
                        ->update(\[
                            'status' => 1,
                            'buyer\_logon\_id' => $\_POST\['buyer\_logon\_id'\],
                            'pay\_time' => $\_POST\['gmt\_payment'\],
                            'pay\_no' => $\_POST\['trade\_no'\]
                        \]);
                    Db::name('user')
                        ->where('uid',$res\['uid'\])
                        ->setInc('integral', floatval($\_POST\['total\_amount'\]) \* 1000);
                }

            }
        }
        echo 'success'; //The interface must return success, otherwise Alibaba will keep sending verification verification.
    }
}

Polling If you need the page to jump synchronously after successful payment, you need to add polling. Since there is no synchronous notification function for face-to-face payment, polling needs to be used, and this method is also mentioned in the documentation for face-to-face payment.
The following code is excerpted from Bty paid version

public function query() { if (input(‘post.no’)) { $out_trade_no = input(‘post.no’);

        $queryContentBuilder = new AlipayTradeQueryContentBuilder();
        $queryContentBuilder->setOutTradeNo($out\_trade\_no);

        $queryResponse = new AlipayTradeService($this->alipay\_config);
        $queryResult = $queryResponse->queryTradeResult($queryContentBuilder);
        $res\['status'\] = $queryResult->getTradeStatus();
        $res\['buyer'\] = isset($queryResult->getResponse()->buyer\_logon\_id) ? $queryResult->getResponse()->buyer\_logon\_id : '';
        $res\['amount'\] = $queryResult->getResponse()->buyer\_pay\_amount;

        if ($res\['status'\] == 'SUCCESS') {
            $this->paySuccess('', $out\_trade\_no);
        }

        exit(json\_encode($res));
    } else {
        $this->error('Illegal request');
    }
}

Since we are not connected to a system similar to a shopping mall, complex operations such as refunds are not needed for the time being.
This concludes the basic matching of face-to-face payments.
Finished spreading flowers!