Parcourir la source

feat(lakala): 新增拉卡拉支付SDK-V2相关类和方法

- 添加 LocationInfo 数据传输对象,用于地理位置信息处理
- 实现 V2LakalaApi 类,支持请求/响应加解密模式
- 新增 V2LakalaNotifyApi 类,用于处理回调通知及验签逻辑
- 创建 V2ModelRequest 和 V2ModelResponse 模型类
- 添加 V2ModelTradeNotify 交易回调模型
- 实现 V2TermExtInfo 终端信息模型类
- 添加 V2TradeLocationInfo位置信息模型类
- 集成 V2HttpService 工具类,支持多种响应格式
- 引入 V2LakalaSM4 加密工具类,实现国密SM4算法- 提供完整的请求封装、签名生成与验证功能
- 支持 HTTPS 请求及 SSL 证书配置- 实现请求随机字符串生成与时间戳管理机制
runphp il y a 4 mois
Parent
commit
136c4a3b70
65 fichiers modifiés avec 6783 ajouts et 4 suppressions
  1. 31 1
      config.php
  2. 38 2
      src/Config.php
  3. 36 0
      src/Dto/LocationInfo.php
  4. 1 1
      src/Extension.php
  5. 15 0
      src/OpenAPISDK/V2/.lklopensdk.env
  6. 284 0
      src/OpenAPISDK/V2/Api/V2LakalaApi.php
  7. 76 0
      src/OpenAPISDK/V2/Api/V2LakalaNotifyApi.php
  8. 153 0
      src/OpenAPISDK/V2/Model/V2ModelRequest.php
  9. 148 0
      src/OpenAPISDK/V2/Model/V2ModelResponse.php
  10. 46 0
      src/OpenAPISDK/V2/Model/V2ModelTradeNotify.php
  11. 166 0
      src/OpenAPISDK/V2/Model/V2TermExtInfo.php
  12. 67 0
      src/OpenAPISDK/V2/Model/V2TradeLocationInfo.php
  13. 163 0
      src/OpenAPISDK/V2/Util/V2HttpService.php
  14. 201 0
      src/OpenAPISDK/V2/Util/V2LakalaSM4.php
  15. 37 0
      src/OpenAPISDK/V2/V2ApiException.php
  16. 87 0
      src/OpenAPISDK/V2/V2Configuration.php
  17. 56 0
      src/OpenAPISDK/V2/V2ObjectSerializer.php
  18. 15 0
      src/OpenAPISDK/V3/.lklopensdk.env
  19. 19 0
      src/OpenAPISDK/V3/Api/EncryptMode.php
  20. 269 0
      src/OpenAPISDK/V3/Api/LakalaApi.php
  21. 76 0
      src/OpenAPISDK/V3/Api/LakalaNotifyApi.php
  22. 26 0
      src/OpenAPISDK/V3/Api/QueryTradequeryApi.php
  23. 25 0
      src/OpenAPISDK/V3/Api/RelationRefundApi.php
  24. 28 0
      src/OpenAPISDK/V3/Api/TransMicropayApi.php
  25. 29 0
      src/OpenAPISDK/V3/Api/TransPreorderApi.php
  26. 37 0
      src/OpenAPISDK/V3/ApiException.php
  27. 87 0
      src/OpenAPISDK/V3/Configuration.php
  28. 94 0
      src/OpenAPISDK/V3/Model/ModelRequest.php
  29. 114 0
      src/OpenAPISDK/V3/Model/ModelResponse.php
  30. 46 0
      src/OpenAPISDK/V3/Model/ModelTradeNotify.php
  31. 95 0
      src/OpenAPISDK/V3/Model/QueryTradequeryRequest.php
  32. 341 0
      src/OpenAPISDK/V3/Model/QueryTradequeryResponse.php
  33. 168 0
      src/OpenAPISDK/V3/Model/RelationRefundRequest.php
  34. 211 0
      src/OpenAPISDK/V3/Model/RelationRefundResponse.php
  35. 24 0
      src/OpenAPISDK/V3/Model/TradeAccBusiFields.php
  36. 23 0
      src/OpenAPISDK/V3/Model/TradeExtendParams.php
  37. 24 0
      src/OpenAPISDK/V3/Model/TradeGoodsDetail.php
  38. 67 0
      src/OpenAPISDK/V3/Model/TradeLocationInfo.php
  39. 122 0
      src/OpenAPISDK/V3/Model/TradeMicropayAlipayAccBusiFields.php
  40. 62 0
      src/OpenAPISDK/V3/Model/TradeMicropayAlipayExtendParams.php
  41. 140 0
      src/OpenAPISDK/V3/Model/TradeMicropayAlipayGoodsDetail.php
  42. 116 0
      src/OpenAPISDK/V3/Model/TradeMicropayWechaAccBusiFields.php
  43. 70 0
      src/OpenAPISDK/V3/Model/TradeMicropayWechaDetail.php
  44. 88 0
      src/OpenAPISDK/V3/Model/TradeMicropayWechaGoodsDetail.php
  45. 148 0
      src/OpenAPISDK/V3/Model/TradePreorderAlipayAccBusiFields.php
  46. 75 0
      src/OpenAPISDK/V3/Model/TradePreorderAlipayExtendParams.php
  47. 140 0
      src/OpenAPISDK/V3/Model/TradePreorderAlipayGoodsDetail.php
  48. 36 0
      src/OpenAPISDK/V3/Model/TradePreorderNucspayAccBusiFields.php
  49. 148 0
      src/OpenAPISDK/V3/Model/TradePreorderUnionPayAccBusiFields.php
  50. 101 0
      src/OpenAPISDK/V3/Model/TradePreorderUnionPayAcqAddnDataGoodsInfo.php
  51. 62 0
      src/OpenAPISDK/V3/Model/TradePreorderUnionPayAcqAddnDataOrderInfo.php
  52. 62 0
      src/OpenAPISDK/V3/Model/TradePreorderUnionPayAddnInfo.php
  53. 88 0
      src/OpenAPISDK/V3/Model/TradePreorderUnionPayRiskInfo.php
  54. 156 0
      src/OpenAPISDK/V3/Model/TradePreorderWechaAccBusiFields.php
  55. 71 0
      src/OpenAPISDK/V3/Model/TradePreorderWechaDetail.php
  56. 89 0
      src/OpenAPISDK/V3/Model/TradePreorderWechaGoodsDetail.php
  57. 306 0
      src/OpenAPISDK/V3/Model/TransMicropayRequest.php
  58. 268 0
      src/OpenAPISDK/V3/Model/TransMicropayResponse.php
  59. 320 0
      src/OpenAPISDK/V3/Model/TransPreorderRequest.php
  60. 107 0
      src/OpenAPISDK/V3/Model/TransPreorderResponse.php
  61. 67 0
      src/OpenAPISDK/V3/ObjectSerializer.php
  62. 163 0
      src/OpenAPISDK/V3/Util/HttpService.php
  63. 201 0
      src/OpenAPISDK/V3/Util/LakalaSM4.php
  64. 128 0
      src/Service/TransactionService.php
  65. 26 0
      tests/Service/TransactionServiceTest.php

+ 31 - 1
config.php

@@ -185,7 +185,37 @@ return json_decode(<<<'JSON'
         "name": "ref_Fognmh2zkilk14qc",
         "display": true,
         "hidden": false
-      }
+      },
+       {
+    "type": "input",
+    "title": "支付回调地址",
+    "field": "notify_url",
+    "value":"https://sixshop.ddev.site/api/lakala/notify",
+    "props": {
+      "placeholder": "请输入支付结果通知回调地址"
+    },
+    "$required": true,
+    "_fc_id": "id_F0j5md1fz7stawc",
+    "name": "ref_Ffipmd1fz7staxc",
+    "_fc_drag_tag": "input",
+    "display": true,
+    "hidden": false
+  },
+  {
+    "type": "input",
+    "title": "收货确认通知地址",
+    "field": "complete_notify_url",
+     "value":"https://sixshop.ddev.site/api/lakala/notify",
+    "props": {
+      "placeholder": "请输入收货确认通知地址"
+    },
+    "$required": true,
+    "_fc_id": "id_F0j5md1fz7stawc",
+    "name": "ref_Ffipmd1fz7staxc",
+    "_fc_drag_tag": "input",
+    "display": true,
+    "hidden": false
+  }
     ],
     "_fc_drag_tag": "elCard",
     "_fc_id": "id_Fpcxmh2zkilk14fc",

+ 38 - 2
src/Config.php

@@ -2,10 +2,16 @@
 
 namespace SixShop\Lakala;
 
-use SixDec\Xlpay\Extension;
+use SixShop\Lakala\Extension;
+use SixShop\Lakala\OpenAPISDK\V3\Configuration;
 use SixShop\System\Trait\ConfigTrait;
 
-class Config
+/**
+ * @property string $term_no 终端号
+ * @property string $notify_url 商户通知地址
+ * @property string $complete_notify_url 收货确认通知地址
+ */
+class Config extends Configuration
 {
     use ConfigTrait;
 
@@ -23,4 +29,34 @@ class Config
             ],
         ];
     }
+
+    #[\Override] public function getAppId()
+    {
+        return $this->getConfig('appid');
+    }
+
+    #[\Override] public function getSerialNo()
+    {
+        return $this->getConfig('serial_no');
+    }
+
+    #[\Override] public function getSm4Key()
+    {
+        return null;
+    }
+
+    #[\Override] public function getMerchantPrivateKeyPath()
+    {
+        return public_path().$this->getConfig('sign_cert')[0];
+    }
+
+    #[\Override] public function getLklCertificatePath()
+    {
+        return public_path().$this->getConfig('verify_cert')[0];
+    }
+
+    #[\Override] public function getHost()
+    {
+        return $this->getConfig('gateway')[$this->getConfig('environment')];
+    }
 }

+ 36 - 0
src/Dto/LocationInfo.php

@@ -0,0 +1,36 @@
+<?php
+declare(strict_types=1);
+namespace SixShop\Lakala\Dto;
+
+class LocationInfo implements \JsonSerializable
+{
+    /**
+     * @param string $requestIP 请求方的IP地址,存在必填,格式如36.45.36.95
+     * @param string $baseStation 客户端设备的基站信息(主扫时基站信息使用该字段)
+     * @param string $location 纬度,经度
+     *
+     * 商户终端的地理位置,银联二维码交易必填,整体格式:纬度,经度,+表示北纬、东经,-表示南纬、 西经。
+     *
+     * 经度格式:1位正负号+3位整数+1位小数点+5位小数;
+     *
+     * 纬度格式:1位正负号+2位整数+1位小数点+6位小数;
+     *
+     * 举例:+31.221345,+121.12345
+     */
+    public function __construct(
+        private string $requestIP,
+        private string $baseStation = '',
+        private string $location = ''
+    )
+    {
+    }
+
+    public function jsonSerialize(): array
+    {
+        return [
+            'requestIP' => $this->requestIP,
+            'baseStation' => $this->baseStation,
+            'location' => $this->location
+        ];
+    }
+}

+ 1 - 1
src/Extension.php

@@ -21,6 +21,6 @@ class Extension extends ExtensionAbstract implements PaymentExtensionInterface
 
     public function getPaymentProvider(): PaymentProviderInterface
     {
-
+        return app(PaymentProvider::class);
     }
 }

+ 15 - 0
src/OpenAPISDK/V2/.lklopensdk.env

@@ -0,0 +1,15 @@
+APP_DEBUG = true
+
+[APP]
+APP_ID      = OP00000003
+SERIAL_NO   = 00dfba8194c41b84cf
+# 生产主机地址
+HOST_PRO    = https://s2.lakala.com
+# 测试主机地址
+HOST_TEST   = https://test.wsmsd.cn/sit
+
+[CERT]
+SM4_KEY = uIj6CPg1GZAY10dXFfsEAQ==
+#相对当前配置文件的路径
+MERCHANT_PRIVATE_KEY_PATH   = ../../example/RSAKeys/DEV/OP00000003_private_key.pem
+LKL_CERTIFICATE_PATH        = ../../example/RSAKeys/DEV/lkl-apigw-v2.cer

+ 284 - 0
src/OpenAPISDK/V2/Api/V2LakalaApi.php

@@ -0,0 +1,284 @@
+<?php
+
+namespace SixShop\Lakala\OpenAPISDK\V2\Api;
+
+use SixShop\Lakala\OpenAPISDK\V2\V2ApiException;
+use SixShop\Lakala\OpenAPISDK\V2\V2ObjectSerializer;
+use SixShop\Lakala\OpenAPISDK\V2\V2Configuration;
+use SixShop\Lakala\OpenAPISDK\V2\Util\V2HttpService;
+use SixShop\Lakala\OpenAPISDK\V2\Util\V2LakalaSM4;
+
+use SixShop\Lakala\OpenAPISDK\V2\Model\V2ModelRequest;
+use SixShop\Lakala\OpenAPISDK\V2\Model\V2ModelResponse;
+
+/**
+ * V2EncryptMode Class
+ * 请求或者响应加解密类型
+ */
+class V2EncryptMode
+{
+    // 普通无加解密:请求为明文,返回也是明文
+    const NONE = 'none';
+    // 只请求加密,返回为明文
+    const REQUEST = 'request';
+    // 请求明文、响应需解密
+    const RESPONSE = 'response';
+    // 请求需加密、返回需解密
+    const BOTH = 'both';
+}
+
+/**
+ * LakalaApi Class
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK\V2\Api
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+class V2LakalaApi
+{
+
+    /**
+     * @var string 随机字符串集
+     */
+    protected $charset = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+
+    /**
+     * 算法
+     * @var string
+     */
+    protected $schema = "LKLAPI-SHA256withRSA";
+
+    // 请求响应body使用SM4加密类型
+    protected $v2EncryptMode;
+
+    protected $config;
+    protected $v2HttpService;
+
+    protected $resourcePath;
+
+    public function __construct(V2Configuration $config, $v2EncryptMode = V2EncryptMode::NONE)
+    {
+        $this->v2EncryptMode = $v2EncryptMode;
+        $this->config = $config;
+        $this->v2HttpService = new \SixShop\Lakala\OpenAPISDK\V2\Util\V2HttpService();
+    }
+
+    public function setResourcePath($resourcePath)
+    {
+        $this->resourcePath = $resourcePath;
+        return $this;
+    }
+    
+    public function getResourcePath()
+    {
+        return $this->resourcePath;
+    }
+
+    public function tradeApi($resourcePath, V2ModelRequest $v2ModelRequest,
+                                            $returnType = '\Lakala\OpenAPISDK\V2\Model\V2ModelResponse',
+                                            $method = 'POST')
+    {
+        $headerParams = [];
+
+        if (!$v2ModelRequest->valid()) {
+            throw new V2ApiException(implode(',', $v2ModelRequest->listInvalidProperties()));
+        }
+
+        $httpBody = $v2ModelRequest->jsonSerialize();
+        if ($httpBody !== null) {
+            $httpBody = json_encode(V2ObjectSerializer::sanitizeForSerialization($httpBody));
+        }
+        return $this->apiWithBody($resourcePath, $httpBody, $headerParams, $method, $returnType);
+    }
+
+    public function apiWithBody($resourcePath, $httpBody, $headerParams = [], $method = 'POST',
+                                               $returnType = '\Lakala\OpenAPISDK\V2\Model\V2ModelResponse')
+    {
+        $this->setResourcePath($resourcePath);
+
+        if (!is_string($httpBody)) {
+            $httpBody = json_encode($httpBody);
+        }
+
+        list($response, $statusCode, $httpHeader) = $this->callApi(
+            $method,
+            $httpBody,
+            $headerParams,
+            $returnType
+        );
+
+        if ($statusCode < 200 || $statusCode > 299) {
+            throw new V2ApiException(
+                sprintf('[%d] 连接API时出错 (%s)', $statusCode, $this->getResourcePath()),
+                $statusCode,
+                $httpHeader,
+                $response
+            );
+        }
+
+        return $response;
+    }
+
+    protected function callApi($method, $httpBody, $headerParams, $returnType)
+    {
+        $request = $this->prepareRequest($method, $httpBody, $headerParams);
+        try {
+            $options = $this->createRequestOptions();
+            $options['header'] = $request['headers'];
+            $options['data'] = $request['body'];
+            $response = $this->v2HttpService->request($method, $request['url'], $options);
+        } catch (Exception $e) {
+            throw new V2ApiException("[{$e->getCode()}] {$e->getMessage()}", $e->getCode(), null, null);
+        }
+
+        $statusCode = $response['info']['http_code'];
+        $responseHeaders = isset($response['header']) ? $response['header'] : null;
+
+        if ($statusCode < 200 || $statusCode > 299) {
+            throw new V2ApiException(
+                sprintf('[%d] 连接API时出错 (%s)', $statusCode, $request['url']),
+                $statusCode,
+                $responseHeaders,
+                $response['body']
+            );
+        }
+
+        $responseVerifySign = $this->responseVerifySign($responseHeaders, $response['body']);
+        if (!$responseVerifySign) {
+            throw new V2ApiException(
+                sprintf('[%d] 验证拉卡拉响应验签错误 (%s)', $statusCode, $request['url']),
+                $statusCode,
+                $responseHeaders,
+                $response['body']
+            );
+        }
+        // 请求咱解密
+        if($this->v2EncryptMode == V2EncryptMode::RESPONSE || $this->v2EncryptMode == V2EncryptMode::BOTH) {
+            // echo "\n<!-- \n" . $response['body'] . "\n--->\n";
+            $sm4 = new V2LakalaSM4();
+            $body = $sm4->decrypt(base64_decode($this->config->getSm4Key()), $response['body']);
+            $response['content'] = json_decode($body);
+            $response['body'] = $body;
+        }
+        $response['content']->originalText = $response['body'];
+
+        return [
+            V2ObjectSerializer::deserialize($response['content'], $returnType, $responseHeaders),
+            $statusCode,
+            $responseHeaders
+        ];
+    }
+
+    protected function prepareRequest($method, $httpBody, $headerParams)
+    {
+        $url = $this->config->getHost() . $this->getResourcePath();
+
+        // SM4加密请求体
+        if($this->v2EncryptMode == V2EncryptMode::REQUEST || $this->v2EncryptMode == V2EncryptMode::BOTH) {
+            $sm4 = new V2LakalaSM4();
+            $httpBody = $sm4->encrypt(base64_decode($this->config->getSm4Key()), $httpBody);
+        }
+
+        $headers = $this->createHeaderParams($headerParams, $httpBody);
+
+        return [
+            'method' => $method,
+            'url' => $url,
+            'headers' => $headers,
+            'body' => $httpBody,
+        ];
+    }
+
+    protected function createHeaderParams($headerParams, $httpBody)
+    {
+        $headers = $this->config->getDefaultHeaders();
+
+        if ($headerParams) {
+            $headers = array_merge($headers, $headerParams);
+        }
+
+        $authorization = $this->getAuthorization($httpBody);
+        $headers[] = 'Content-Type: application/json';
+        $headers[] = "Authorization: $authorization";
+
+        return $headers;
+    }
+
+    protected function createRequestOptions()
+    {
+        $options = [
+            'timeout' => 10,
+            'respond_type' => \Lakala\OpenAPISDK\V2\Util\V2HttpService::RESPOND_TYPE_ARRAY,
+        ];
+
+        return $options;
+    }
+
+    protected function getAuthorization($body)
+    {
+        $randomString = $this->getRandomString(12);
+        $timestamp = time();
+        $data = $this->config->getAppId() . "\n"
+                . $this->config->getSerialNo() . "\n"
+                . $timestamp . "\n"
+                . $randomString . "\n"
+                . $body . "\n";
+
+        $sign = $this->rsaSign($data);
+
+        $authorization = $this->schema . " appid=\"" . $this->config->getAppId() . "\","
+                        . "serial_no=\"" . $this->config->getSerialNo() . "\","
+                        . "timestamp=\"" . $timestamp . "\","
+                        . "nonce_str=\"" . $randomString . "\","
+                        . "signature=\"" . $sign . "\"";
+
+        return $authorization;
+    }
+
+    protected function getRandomString($length = 10) {
+        $randomString = '';
+        $charsetLength = strlen($this->charset);
+    
+        // 生成随机字符串
+        for ($i = 0; $i < $length; $i++) {
+            $randomChar = $this->charset[rand(0, $charsetLength - 1)];  // 随机选择字符
+            $randomString .= $randomChar;
+        }
+    
+        return $randomString;
+    }
+
+    //生成 sha256WithRSA 签名
+    protected function rsaSign($content) {
+        $privateContent = file_get_contents($this->config->getMerchantPrivateKeyPath());
+        $privateKey = openssl_pkey_get_private($privateContent);
+        if (!$privateKey) {
+            throw new V2ApiException('获取私钥失败');
+        }
+        $res = openssl_sign($content, $sign, $privateKey, OPENSSL_ALGO_SHA256);
+        if (function_exists('openssl_free_key')) {
+            openssl_free_key($privateKey);
+        }
+        if (!$res) {
+            throw new V2ApiException('[10004] 拉卡拉字符串签名失败');
+        }
+        return base64_encode($sign);
+    }
+
+    protected function responseVerifySign($headers, $body) {
+        $sign = $headers['Lklapi-Signature'];
+        $sign = base64_decode($sign);
+        $data = $headers['Lklapi-Appid'] . "\n"
+                . $headers['Lklapi-Serial'] . "\n"
+                . $headers['Lklapi-Timestamp'] . "\n"
+                . $headers['Lklapi-Nonce'] . "\n"
+                . $body . "\n";
+        // $dir = dirname(__FILE__);
+        // $dir = str_replace('/src/Api', '', $dir);
+        $certContent = file_get_contents($this->config->getLklCertificatePath());
+        $key = openssl_pkey_get_public($certContent);
+        $result = openssl_verify($data, $sign, $key, OPENSSL_ALGO_SHA256) === 1;
+        return $result;
+    }
+}

+ 76 - 0
src/OpenAPISDK/V2/Api/V2LakalaNotifyApi.php

@@ -0,0 +1,76 @@
+<?php
+
+namespace SixShop\Lakala\OpenAPISDK\V2\Api;
+
+use SixShop\Lakala\OpenAPISDK\V2\V2ApiException;
+use SixShop\Lakala\OpenAPISDK\V2\V2Configuration;
+use SixShop\Lakala\OpenAPISDK\V2\V2ObjectSerializer;
+
+use SixShop\Lakala\OpenAPISDK\V2\Model\V2ModelTradeNotify;
+
+/**
+ * V2LakalaNotifyApi Class
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK\V2\Api
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+class V2LakalaNotifyApi
+{
+    protected $config;
+
+    public function __construct(V2Configuration $config)
+    {
+        $this->config = $config;
+    }
+
+    public function notiApi() {
+        $headers = getallheaders();
+        $body = file_get_contents('php://input');
+
+        $verify = $this->requestVerifySign($headers['Authorization'], $body);
+
+        if (!$verify) {
+            throw new V2ApiException('验证拉卡拉验签错误');
+        }
+
+        $notify = new \SixShop\Lakala\OpenAPISDK\V2\Model\V2ModelTradeNotify();
+        $notify->setHeaders($headers);
+        $notify->setOriginalText($body);
+
+        return $notify;
+    }
+
+    protected function requestVerifySign($authorization, $body) {
+        $pattern = '/timestamp="(\d+)",nonce_str="(\w+)",signature="([^"]+)"/';
+        preg_match($pattern, $authorization, $matches);
+        if (count($matches) == 0) return false;
+
+        $timestamp = $matches[1];
+        $nonce_str = $matches[2];
+        $signature = $matches[3];
+
+        $sign = base64_decode($signature);
+        $data = $timestamp. "\n"
+                . $nonce_str . "\n"
+                . $body . "\n";
+
+        $certContent = file_get_contents($this->config->getLklCertificatePath());
+        $key = openssl_pkey_get_public($certContent);
+        $result = openssl_verify($data, $sign, $key, OPENSSL_ALGO_SHA256) === 1;
+        return $result;
+    }
+
+    public function success() {
+        echo '{"code":"SUCCESS","message":"执行成功"}';
+    }
+
+    public function fail($msg) {
+        $ret = [
+            'code' => 'FAIL',
+            'message' => $msg
+        ];
+        echo json_encode($ret, JSON_UNESCAPED_UNICODE);
+    }
+}

+ 153 - 0
src/OpenAPISDK/V2/Model/V2ModelRequest.php

@@ -0,0 +1,153 @@
+<?php
+/**
+ * V2ModelRequest
+ * PHP version 7.4
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK\V2\Model
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+
+namespace SixShop\Lakala\OpenAPISDK\V2\Model;
+
+class V2ModelRequest {
+    // 请求时间	M	String(14)	请求时间,格式yyyyMMddHHmmss
+    protected $timestamp;
+    // 随机数	C	String(32)	随机数
+    protected $rnd;
+    // 版本号	C	String(6)	1.0.0
+    protected $ver;
+    // 请求序列号	C	String(32)	-
+    protected $reqId;
+    // 请求参数	M	Object	参见各个接口的请求参数格式
+    protected $reqData;
+    // 地址位置信息	M	Object
+    protected $locationInfo;
+    // 终端信息	M	Object	无特殊处理需求或无终端信息,填写”termExtInfo”:{}
+    protected $termExtInfo;
+
+    public function __construct()
+    {
+        $this->ver = '1.0.0';
+
+        $mictime = microtime();
+        list($usec, $sec) = explode(" ", $mictime);
+        $this->timestamp = $sec . substr($usec, 2, 3);
+
+        $this->reqData = [];
+    }
+
+    public function setTimestamp($timestamp)
+    {
+        $this->timestamp = $timestamp;
+        return $this;
+    }
+    
+    public function getTimestamp()
+    {
+        return $this->timestamp;
+    }
+    
+    public function setRnd($rnd)
+    {
+        $this->rnd = $rnd;
+        return $this;
+    }
+    
+    public function getRnd()
+    {
+        return $this->rnd;
+    }
+    
+    public function setVer($ver)
+    {
+        $this->ver = $ver;
+        return $this;
+    }
+    
+    public function getVer()
+    {
+        return $this->ver;
+    }
+    
+    public function setReqId($reqId)
+    {
+        $this->reqId = $reqId;
+        return $this;
+    }
+    
+    public function getReqId()
+    {
+        return $this->reqId;
+    }
+    
+    public function setReqData($reqData)
+    {
+        $this->reqData = $reqData;
+        return $this;
+    }
+    
+    public function getReqData()
+    {
+        return $this->reqData;
+    }
+    
+    public function setLocationInfo($locationInfo)
+    {
+        $this->locationInfo = $locationInfo;
+        return $this;
+    }
+    
+    public function getLocationInfo()
+    {
+        return $this->locationInfo;
+    }
+    
+    public function setTermExtInfo($termExtInfo)
+    {
+        $this->termExtInfo = $termExtInfo;
+        return $this;
+    }
+    
+    public function getTermExtInfo()
+    {
+        return $this->termExtInfo;
+    }
+
+    /**
+     * Show all the invalid properties with reasons.
+     *
+     * @return array invalid properties with reasons
+     */
+    public function listInvalidProperties()
+    {
+        $invalidProperties = [];
+
+        return $invalidProperties;
+    }
+
+    /**
+     * Validate all the properties in the model
+     * return true if all passed
+     *
+     * @return bool True if all properties are valid
+     */
+    public function valid()
+    {
+        return count($this->listInvalidProperties()) === 0;
+    }
+
+    public function jsonSerialize()
+    {
+        return [
+            'timestamp' => $this->timestamp,
+            'rnd' => $this->rnd,
+            'ver' => $this->ver,
+            'reqId' => $this->reqId,
+            'reqData' => $this->reqData,
+            'locationInfo' => $this->locationInfo === null ? $this->locationInfo : $this->locationInfo->jsonSerialize(),
+            'termExtInfo' => $this->termExtInfo === null ? $this->termExtInfo : $this->termExtInfo->jsonSerialize(),
+        ];
+    }
+}

+ 148 - 0
src/OpenAPISDK/V2/Model/V2ModelResponse.php

@@ -0,0 +1,148 @@
+<?php
+/**
+ * V2ModelResponse
+ * PHP version 7.4
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK\V2\Model
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+
+namespace SixShop\Lakala\OpenAPISDK\V2\Model;
+
+class V2ModelResponse {
+    protected $retCode;
+    protected $retMsg;
+    protected $sign;
+    protected $timestamp;
+    protected $rnd;
+    protected $reqId;
+    protected $respId;
+    protected $ver;
+    protected $respData;
+    
+    public function setRetCode($retCode)
+    {
+        $this->retCode = $retCode;
+        return $this;
+    }
+    
+    public function getRetCode()
+    {
+        return $this->retCode;
+    }
+    
+    public function setRetMsg($retMsg)
+    {
+        $this->retMsg = $retMsg;
+        return $this;
+    }
+    
+    public function getRetMsg()
+    {
+        return $this->retMsg;
+    }
+    
+    public function setSign($sign)
+    {
+        $this->sign = $sign;
+        return $this;
+    }
+    
+    public function getSign()
+    {
+        return $this->sign;
+    }
+    
+    public function setTimestamp($timestamp)
+    {
+        $this->timestamp = $timestamp;
+        return $this;
+    }
+    
+    public function getTimestamp()
+    {
+        return $this->timestamp;
+    }
+    
+    public function setRnd($rnd)
+    {
+        $this->rnd = $rnd;
+        return $this;
+    }
+    
+    public function getRnd()
+    {
+        return $this->rnd;
+    }
+    
+    public function setReqId($reqId)
+    {
+        $this->reqId = $reqId;
+        return $this;
+    }
+    
+    public function getReqId()
+    {
+        return $this->reqId;
+    }
+    
+    public function setRespId($respId)
+    {
+        $this->respId = $respId;
+        return $this;
+    }
+    
+    public function getRespId()
+    {
+        return $this->respId;
+    }
+    
+    public function setVer($ver)
+    {
+        $this->ver = $ver;
+        return $this;
+    }
+    
+    public function getVer()
+    {
+        return $this->ver;
+    }
+    
+    public function setRespData($respData)
+    {
+        $this->respData = $respData;
+        return $this;
+    }
+    
+    public function getRespData()
+    {
+        return $this->respData;
+    }
+
+    public function setHeaders($headers)
+    {
+        $this->headers = $headers;
+        return $this;
+    }
+    
+    public function getHeaders()
+    {
+        return $this->headers;
+    }
+
+    public function setOriginalText($originalText) {
+        $this->originalText = $originalText;
+        return $this;
+    }
+
+    public function getOriginalText() {
+        return $this->originalText;
+    }
+
+    public function jsonSerialize()
+    {
+       return V2ObjectSerializer::sanitizeForSerialization($this);
+    }
+}

+ 46 - 0
src/OpenAPISDK/V2/Model/V2ModelTradeNotify.php

@@ -0,0 +1,46 @@
+<?php
+/**
+ * V2ModelTradeNotify
+ * 交易回调Model
+ * 
+ * PHP version 7.4
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK\V2\Model
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+
+namespace SixShop\Lakala\OpenAPISDK\V2\Model;
+
+class V2ModelTradeNotify {
+    // 响应头信息
+    protected $headers;
+    // 响应原文
+    protected $originalText;
+
+    public function setHeaders($headers)
+    {
+        $this->headers = $headers;
+        return $this;
+    }
+    
+    public function getHeaders()
+    {
+        return $this->headers;
+    }
+
+    public function setOriginalText($originalText) {
+        $this->originalText = $originalText;
+        return $this;
+    }
+
+    public function getOriginalText() {
+        return $this->originalText;
+    }
+
+    public function jsonSerialize()
+    {
+       return ObjectSerializer::sanitizeForSerialization($this);
+    }
+}

+ 166 - 0
src/OpenAPISDK/V2/Model/V2TermExtInfo.php

@@ -0,0 +1,166 @@
+<?php
+/**
+ * 终端信息
+ * V2TermExtInfo
+ * PHP version 7.4
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK\V2\Model
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+
+namespace SixShop\Lakala\OpenAPISDK\V2\Model;
+
+class V2TermExtInfo implements \JsonSerializable
+{
+    protected $termSN;
+    protected $termBaseStation;
+    protected $termLoc;
+    protected $termIp;
+    protected $termSerialNo;
+    protected $termType;
+    protected $termModel;
+    protected $termManu;
+    protected $appCode;
+    protected $appVer;
+    protected $termFP;
+    
+    public function setTermSN($termSN)
+    {
+        $this->termSN = $termSN;
+        return $this;
+    }
+    
+    public function getTermSN()
+    {
+        return $this->termSN;
+    }
+    
+    public function setTermBaseStation($termBaseStation)
+    {
+        $this->termBaseStation = $termBaseStation;
+        return $this;
+    }
+    
+    public function getTermBaseStation()
+    {
+        return $this->termBaseStation;
+    }
+    
+    public function setTermLoc($termLoc)
+    {
+        $this->termLoc = $termLoc;
+        return $this;
+    }
+    
+    public function getTermLoc()
+    {
+        return $this->termLoc;
+    }
+    
+    public function setTermIp($termIp)
+    {
+        $this->termIp = $termIp;
+        return $this;
+    }
+    
+    public function getTermIp()
+    {
+        return $this->termIp;
+    }
+    
+    public function setTermSerialNo($termSerialNo)
+    {
+        $this->termSerialNo = $termSerialNo;
+        return $this;
+    }
+    
+    public function getTermSerialNo()
+    {
+        return $this->termSerialNo;
+    }
+    
+    public function setTermType($termType)
+    {
+        $this->termType = $termType;
+        return $this;
+    }
+    
+    public function getTermType()
+    {
+        return $this->termType;
+    }
+    
+    public function setTermModel($termModel)
+    {
+        $this->termModel = $termModel;
+        return $this;
+    }
+    
+    public function getTermModel()
+    {
+        return $this->termModel;
+    }
+    
+    public function setTermManu($termManu)
+    {
+        $this->termManu = $termManu;
+        return $this;
+    }
+    
+    public function getTermManu()
+    {
+        return $this->termManu;
+    }
+    
+    public function setAppCode($appCode)
+    {
+        $this->appCode = $appCode;
+        return $this;
+    }
+    
+    public function getAppCode()
+    {
+        return $this->appCode;
+    }
+    
+    public function setAppVer($appVer)
+    {
+        $this->appVer = $appVer;
+        return $this;
+    }
+    
+    public function getAppVer()
+    {
+        return $this->appVer;
+    }
+    
+    public function setTermFP($termFP)
+    {
+        $this->termFP = $termFP;
+        return $this;
+    }
+    
+    public function getTermFP()
+    {
+        return $this->termFP;
+    }
+
+    public function jsonSerialize()
+    {
+        return [
+            'termSN' => $this->termSN,
+            'termBaseStation' => $this->termBaseStation,
+            'termLoc' => $this->termLoc,
+            'termIp' => $this->termIp,
+            'termSerialNo' => $this->termSerialNo,
+            'termType' => $this->termType,
+            'termModel' => $this->termModel,
+            'termManu' => $this->termManu,
+            'appCode' => $this->appCode,
+            'appVer' => $this->appVer,
+            'termFP' => $this->termFP,
+        ];
+    }
+}

+ 67 - 0
src/OpenAPISDK/V2/Model/V2TradeLocationInfo.php

@@ -0,0 +1,67 @@
+<?php
+/**
+ * 地址位置信息
+ * TradeLocationInfo
+ * PHP version 7.4
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK\V2\Model
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+
+namespace SixShop\Lakala\OpenAPISDK\V2\Model;
+
+class V2TradeLocationInfo implements \JsonSerializable
+{
+    protected $requestIp;
+    protected $baseStation;
+    protected $location;
+
+    public function __construct($ip)
+    {
+        $this->requestIp = $ip;
+    }
+
+    public function setRequestIp($requestIp)
+    {
+        $this->requestIp = $requestIp;
+        return $this;
+    }
+
+    public function getRequestIp()
+    {
+        return $this->requestIp;
+    }
+
+    public function setBaseStation($baseStation)
+    {
+        $this->baseStation = $baseStation;
+        return $this;
+    }
+
+    public function getBaseStation()
+    {
+        return $this->baseStation;
+    }
+
+    public function setLocation($location)
+    {
+        $this->location = $location;
+        return $this;
+    }
+
+    public function getLocation()
+    {
+        return $this->location;
+    }
+
+    public function jsonSerialize()
+    {
+        return [
+            'requestIp' => $this->requestIp,
+            'baseStation' => $this->baseStation,
+            'location' => $this->location,
+        ];
+    }
+}

+ 163 - 0
src/OpenAPISDK/V2/Util/V2HttpService.php

@@ -0,0 +1,163 @@
+<?php
+/**
+ * V2HttpService
+ * PHP version 7.4
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK\V2\Util
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+
+namespace SixShop\Lakala\OpenAPISDK\V2\Util;
+
+/**
+ * HTTP请求服务
+ * class V2HttpService
+ * @package service
+ */
+class V2HttpService
+{
+    // Response types
+    const RESPOND_TYPE_SIMPLE = 'simple';
+    const RESPOND_TYPE_ORIGINAL = 'original';
+    const RESPOND_TYPE_ARRAY = 'array';
+    const RESPOND_TYPE_JSON = 'json';
+
+    /**
+     * Send a GET request
+     * @param string $url HTTP request URL
+     * @param array $query GET request parameters
+     * @param array $options CURL options
+     * @return mixed
+     */
+    public static function get($url, array $query = [], array $options = [])
+    {
+        $options['query'] = $query;
+        return self::request('GET', $url, $options);
+    }
+
+    /**
+     * Send a POST request
+     * @param string $url HTTP request URL
+     * @param array $data POST request data
+     * @param array $options CURL options
+     * @return mixed
+     */
+    public static function post($url, array $data = [], array $options = [])
+    {
+        $options['data'] = $data;
+        return self::request('POST', $url, $options);
+    }
+
+    /**
+     * Send a HTTP request
+     * @param string $method Request method
+     * @param string $url Request URL
+     * @param array $options Request options [header, data, ssl_cer, ssl_key, respond_type]
+     * @return mixed
+     */
+    public static function request($method, $url, array $options = [])
+    {
+        $curl = curl_init();
+// print_r($options);
+        // Set GET parameters
+        if (!empty($options['query'])) {
+            $url .= (strpos($url, '?') === false ? '?' : '&') . http_build_query($options['query']);
+        }
+
+        // Set POST data
+        if (strtoupper($method) === 'POST') {
+            curl_setopt($curl, CURLOPT_POST, true);
+            curl_setopt($curl, CURLOPT_POSTFIELDS, $options['data']);
+        }
+
+        // Set request timeout
+        curl_setopt($curl, CURLOPT_TIMEOUT, $options['timeout'] ?? 60);
+
+        // Set response type
+        $validRespondTypes = [self::RESPOND_TYPE_SIMPLE, self::RESPOND_TYPE_ORIGINAL, self::RESPOND_TYPE_ARRAY, self::RESPOND_TYPE_JSON];
+        $respondType = in_array($options['respond_type'] ?? '', $validRespondTypes) ? $options['respond_type'] : self::RESPOND_TYPE_SIMPLE;
+
+        // Set headers
+        if (!empty($options['header'])) {
+            curl_setopt($curl, CURLOPT_HTTPHEADER, $options['header']);
+        }
+
+        // Set SSL certificates
+        if (!empty($options['ssl_cer']) && file_exists($options['ssl_cer'])) {
+            curl_setopt($curl, CURLOPT_SSLCERTTYPE, 'PEM');
+            curl_setopt($curl, CURLOPT_SSLCERT, $options['ssl_cer']);
+        }
+        if (!empty($options['ssl_key']) && file_exists($options['ssl_key'])) {
+            curl_setopt($curl, CURLOPT_SSLKEYTYPE, 'PEM');
+            curl_setopt($curl, CURLOPT_SSLKEY, $options['ssl_key']);
+        }
+
+        // Set common options
+        curl_setopt($curl, CURLOPT_URL, $url);
+        curl_setopt($curl, CURLOPT_HEADER, $respondType !== self::RESPOND_TYPE_SIMPLE);
+        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
+        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
+        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
+
+        $content = curl_exec($curl);
+        $curlInfo = curl_getinfo($curl);
+
+        // Remove BOM header
+        $content = preg_replace('/^\xEF\xBB\xBF/', '', $content);
+
+        $response = self::formatResponse($content, $curlInfo, $respondType);
+        curl_close($curl);
+        return $response;
+    }
+
+    /**
+     * Format response based on respond type
+     * @param string $content
+     * @param array $curlInfo
+     * @param string $respondType
+     * @return mixed
+     */
+    private static function formatResponse($content, $curlInfo, $respondType)
+    {
+        switch ($respondType) {
+            case self::RESPOND_TYPE_ORIGINAL:
+                return $content;
+            case self::RESPOND_TYPE_ARRAY:
+            case self::RESPOND_TYPE_JSON:
+                $headerSize = $curlInfo['header_size'];
+                $header = substr($content, 0, $headerSize);
+                $body = substr($content, $headerSize);
+                $response = [
+                    'body' => $body,
+                    'content' => json_decode($body),
+                    'info' => $curlInfo,
+                    'header' => self::parseResponseHeader($header)
+                ];
+                return $respondType === self::RESPOND_TYPE_JSON ? json_encode($response) : $response;
+            default:
+                return $content;
+        }
+    }
+
+    /**
+     * Parse response header
+     * @param string $header
+     * @return array
+     */
+    private static function parseResponseHeader($header)
+    {
+        $headers = [];
+        $headerLines = explode("\r\n", $header);
+        $headers['status'] = $headerLines[0];
+        array_shift($headerLines);
+        foreach ($headerLines as $line) {
+            if (strpos($line, ':') !== false) {
+                list($key, $value) = explode(':', $line, 2);
+                $headers[ucwords(trim($key), '-')] = trim($value);
+            }
+        }
+        return $headers;
+    }
+}

+ 201 - 0
src/OpenAPISDK/V2/Util/V2LakalaSM4.php

@@ -0,0 +1,201 @@
+<?php
+/**
+ * V2LakalaSM4
+ * PHP version 7.4
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK\V2\Util
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+
+namespace SixShop\Lakala\OpenAPISDK\V2\Util;
+
+class V2LakalaSM4
+{
+    const SM4_CK = [
+        0x00070e15, 0x1c232a31, 0x383f464d, 0x545b6269,
+        0x70777e85, 0x8c939aa1, 0xa8afb6bd, 0xc4cbd2d9,
+        0xe0e7eef5, 0xfc030a11, 0x181f262d, 0x343b4249,
+        0x50575e65, 0x6c737a81, 0x888f969d, 0xa4abb2b9,
+        0xc0c7ced5, 0xdce3eaf1, 0xf8ff060d, 0x141b2229,
+        0x30373e45, 0x4c535a61, 0x686f767d, 0x848b9299,
+        0xa0a7aeb5, 0xbcc3cad1, 0xd8dfe6ed, 0xf4fb0209,
+        0x10171e25, 0x2c333a41, 0x484f565d, 0x646b7279
+    ];
+
+    const SM4_SBOX = [
+        0xd6,0x90,0xe9,0xfe,0xcc,0xe1,0x3d,0xb7,0x16,0xb6,0x14,0xc2,0x28,0xfb,0x2c,0x05,
+        0x2b,0x67,0x9a,0x76,0x2a,0xbe,0x04,0xc3,0xaa,0x44,0x13,0x26,0x49,0x86,0x06,0x99,
+        0x9c,0x42,0x50,0xf4,0x91,0xef,0x98,0x7a,0x33,0x54,0x0b,0x43,0xed,0xcf,0xac,0x62,
+        0xe4,0xb3,0x1c,0xa9,0xc9,0x08,0xe8,0x95,0x80,0xdf,0x94,0xfa,0x75,0x8f,0x3f,0xa6,
+        0x47,0x07,0xa7,0xfc,0xf3,0x73,0x17,0xba,0x83,0x59,0x3c,0x19,0xe6,0x85,0x4f,0xa8,
+        0x68,0x6b,0x81,0xb2,0x71,0x64,0xda,0x8b,0xf8,0xeb,0x0f,0x4b,0x70,0x56,0x9d,0x35,
+        0x1e,0x24,0x0e,0x5e,0x63,0x58,0xd1,0xa2,0x25,0x22,0x7c,0x3b,0x01,0x21,0x78,0x87,
+        0xd4,0x00,0x46,0x57,0x9f,0xd3,0x27,0x52,0x4c,0x36,0x02,0xe7,0xa0,0xc4,0xc8,0x9e,
+        0xea,0xbf,0x8a,0xd2,0x40,0xc7,0x38,0xb5,0xa3,0xf7,0xf2,0xce,0xf9,0x61,0x15,0xa1,
+        0xe0,0xae,0x5d,0xa4,0x9b,0x34,0x1a,0x55,0xad,0x93,0x32,0x30,0xf5,0x8c,0xb1,0xe3,
+        0x1d,0xf6,0xe2,0x2e,0x82,0x66,0xca,0x60,0xc0,0x29,0x23,0xab,0x0d,0x53,0x4e,0x6f,
+        0xd5,0xdb,0x37,0x45,0xde,0xfd,0x8e,0x2f,0x03,0xff,0x6a,0x72,0x6d,0x6c,0x5b,0x51,
+        0x8d,0x1b,0xaf,0x92,0xbb,0xdd,0xbc,0x7f,0x11,0xd9,0x5c,0x41,0x1f,0x10,0x5a,0xd8,
+        0x0a,0xc1,0x31,0x88,0xa5,0xcd,0x7b,0xbd,0x2d,0x74,0xd0,0x12,0xb8,0xe5,0xb4,0xb0,
+        0x89,0x69,0x97,0x4a,0x0c,0x96,0x77,0x7e,0x65,0xb9,0xf1,0x09,0xc5,0x6e,0xc6,0x84,
+        0x18,0xf0,0x7d,0xec,0x3a,0xdc,0x4d,0x20,0x79,0xee,0x5f,0x3e,0xd7,0xcb,0x39,0x48
+    ];
+
+    const SM4_FK = [0xA3B1BAC6, 0x56AA3350, 0x677D9197, 0xB27022DC];
+
+    public $_rk = [];
+    public $_block_size = 16;
+
+    public function __construct()
+    {
+    }
+
+    /**
+     * sm4加密(ecb)
+     * @param $key 16位十六进制的字符,比如asw34a5ses5w81wf
+     * @param $data 原始数据
+     * @return string
+     */
+    public function encrypt($key, $data)
+    {
+        $this->sm4KeySchedule($key);
+
+        $bytes = $this->pad($data, $this->_block_size);
+        $chunks = array_chunk($bytes, $this->_block_size);
+
+        $ciphertext = "";
+        foreach ($chunks as $chunk) {
+            $ciphertext .= $this->sm4Encrypt($chunk);
+        }
+
+        return base64_encode($ciphertext);
+    }
+
+    /**
+     * sm4解密
+     * @param $key
+     * @param $data
+     * @return bool|string
+     */
+    public function decrypt($key, $data)
+    {
+        $data = base64_decode($data);
+        if (strlen($data) < 0 || strlen($data) % $this->_block_size != 0) {
+            return false;
+        }
+
+        $this->sm4KeySchedule($key);
+        $bytes = unpack("C*", $data);
+        $chunks = array_chunk($bytes, $this->_block_size);
+
+        $plaintext = "";
+        foreach ($chunks as $chunk) {
+            $plaintext .= substr($this->sm4Decrypt($chunk), 0, 16);
+        }
+        $plaintext = $this->unPad($plaintext);
+
+        return $plaintext;
+    }
+
+    private function sm4Decrypt($cipherText)
+    {
+        $x = [];
+        for ($j=0; $j<4; $j++) {
+            $x[$j]=($cipherText[$j*4]<<24)  |($cipherText[$j*4+1]<<16)| ($cipherText[$j*4+2]<<8)|($cipherText[$j*4+3]);
+        }
+
+        for ($i=0; $i<32; $i++) {
+            $tmp = $x[$i+1]^$x[$i+2]^$x[$i+3]^$this->_rk[31-$i];
+            $buf= (self::SM4_SBOX[($tmp >> 24) & 0xFF]) << 24 |(self::SM4_SBOX[($tmp >> 16) & 0xFF]) << 16 |(self::SM4_SBOX[($tmp >> 8) & 0xFF]) << 8 |(self::SM4_SBOX[$tmp & 0xFF]);
+            $x[$i+4]=$x[$i]^($buf^$this->sm4Rotl32(($buf), 2)^ $this->sm4Rotl32(($buf), 10) ^ $this->sm4Rotl32(($buf), 18)^ $this->sm4Rotl32(($buf), 24));
+        }
+
+        $plainText = [];
+        for ($k=0; $k<4; $k++) {
+            $plainText[4*$k]=($x[35-$k]>> 24)& 0xFF;
+            $plainText[4*$k+1]=($x[35-$k]>> 16)& 0xFF;
+            $plainText[4*$k+2]=($x[35-$k]>> 8)& 0xFF;
+            $plainText[4*$k+3]=($x[35-$k])& 0xFF;
+        }
+
+        return $this->bytesToString($plainText);
+    }
+
+    private function sm4Encrypt($plainText)
+    {
+        $x = [];
+        for ($j=0; $j<4; $j++) {
+            $x[$j]=($plainText[$j*4]<<24)  |($plainText[$j*4+1]<<16)| ($plainText[$j*4+2]<<8)|($plainText[$j*4+3]);
+        }
+
+        for ($i=0; $i<32; $i++) {
+            $tmp = $x[$i+1]^$x[$i+2]^$x[$i+3]^$this->_rk[$i];
+            $buf= (self::SM4_SBOX[($tmp >> 24) & 0xFF]) << 24 |(self::SM4_SBOX[($tmp >> 16) & 0xFF]) << 16 |(self::SM4_SBOX[($tmp >> 8) & 0xFF]) << 8 |(self::SM4_SBOX[$tmp & 0xFF]);
+            $x[$i+4]=$x[$i]^($buf^$this->sm4Rotl32(($buf), 2)^ $this->sm4Rotl32(($buf), 10) ^ $this->sm4Rotl32(($buf), 18)^ $this->sm4Rotl32(($buf), 24));
+        }
+
+        $cipherText = [];
+        for ($k=0; $k<4; $k++) {
+            $cipherText[4*$k]=($x[35-$k]>> 24)& 0xFF;
+            $cipherText[4*$k+1]=($x[35-$k]>> 16)& 0xFF;
+            $cipherText[4*$k+2]=($x[35-$k]>> 8)& 0xFF;
+            $cipherText[4*$k+3]=($x[35-$k])& 0xFF;
+        }
+
+        return $this->bytesToString($cipherText);
+    }
+
+    private function stringToBytes($string)
+    {
+        return unpack('C*', $string);
+    }
+
+    private function bytesToString($bytes)
+    {
+        return vsprintf(str_repeat('%c', count($bytes)), $bytes);
+    }
+
+    private function pad($data)
+    {
+        $bytes = $this->stringToBytes($data);
+        $rem = $this->_block_size - count($bytes) % $this->_block_size;
+        for ($i = 0; $i < $rem; $i++) {
+            array_push($bytes, $rem);
+        }
+        return $bytes;
+    }
+
+    private function unPad($data)
+    {
+        $bytes = $this->stringToBytes($data);
+        $rem = $bytes[count($bytes)];
+        $bytes = array_slice($bytes, 0, count($bytes) - $rem);
+        return $this->bytesToString($bytes);
+    }
+
+    private function sm4Rotl32($buf, $n)
+    {
+        return (($buf << $n) & 0xffffffff) | ($buf >> (32-$n));
+    }
+
+    private function sm4KeySchedule($key)
+    {
+        $this->_rk = [];
+        $key = array_values(unpack("C*", $key));
+
+        $k = [];
+        for ($i=0; $i<4; $i++) {
+            $k[$i] = self::SM4_FK[$i]^(($key[4*$i]<<24) | ($key[4*$i+1]<<16) |($key[4*$i+2]<<8) | ($key[4*$i+3]));
+        }
+
+        for ($j=0; $j<32; $j++) {
+            $tmp = $k[$j+1]^$k[$j+2]^$k[$j+3]^ self::SM4_CK[$j];
+            $buf = (self::SM4_SBOX[($tmp >> 24) & 0xFF]) << 24 |(self::SM4_SBOX[($tmp >> 16) & 0xFF]) << 16 |(self::SM4_SBOX[($tmp >> 8) & 0xFF]) << 8 |(self::SM4_SBOX[$tmp & 0xFF]);
+
+            $k[$j+4]=$k[$j]^(($buf)^($this->sm4Rotl32(($buf), 13))^($this->sm4Rotl32(($buf), 23)));
+            $this->_rk[$j]=$k[$j+4];
+        }
+    }
+}

+ 37 - 0
src/OpenAPISDK/V2/V2ApiException.php

@@ -0,0 +1,37 @@
+<?php
+/**
+ * V2ApiException
+ * PHP version 7.4
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK\V2
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+
+namespace SixShop\Lakala\OpenAPISDK\V2;
+
+use Exception;
+
+class V2ApiException extends Exception
+{
+    protected $responseBody;
+    protected $responseHeaders;
+
+    public function __construct($message = "", $code = 0, $responseBody = null, $responseHeaders = null)
+    {
+        parent::__construct($message, $code);
+        $this->responseBody = $responseBody;
+        $this->responseHeaders = $responseHeaders;
+    }
+
+    public function getResponseBody()
+    {
+        return $this->responseBody;
+    }
+
+    public function getResponseHeaders()
+    {
+        return $this->responseHeaders;
+    }
+}

+ 87 - 0
src/OpenAPISDK/V2/V2Configuration.php

@@ -0,0 +1,87 @@
+<?php
+/**
+ * V2Configuration
+ * PHP version 7.4
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK\V2
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+
+namespace SixShop\Lakala\OpenAPISDK\V2;
+
+class V2Configuration
+{
+    private $config;
+
+    public function __construct($configfile = '.lklopensdk.env')
+    {
+        if (is_string($configfile)) {
+            $dir = dirname(__FILE__);
+            $this->config = $this->readenv($dir . '/' . $configfile);
+            $this->config['app_debug'] = strtolower($this->config['app_debug']);
+            $this->config['merchant_private_key_path'] = realpath($dir . '/' . $this->config['merchant_private_key_path']);
+            $this->config['lkl_certificate_path'] = realpath($dir . '/' . $this->config['lkl_certificate_path']);
+        }
+        else if (is_array($configfile)) {
+            $this->config = $configfile;
+        }
+    }
+
+    public function getAppId() {
+        return $this->getConfig('app_id');
+    }
+
+    public function getSerialNo() {
+        return $this->getConfig('serial_no');
+    }
+
+    public function getSm4Key() {
+        return $this->getConfig('sm4_key');
+    }
+
+    public function getMerchantPrivateKeyPath() {
+        return $this->getConfig('merchant_private_key_path');
+    }
+
+    public function getLklCertificatePath() {
+        return $this->getConfig('lkl_certificate_path');
+    }
+
+    private function getConfig($key) {
+        return $this->config[$key];
+    }
+
+    public function getHost() {
+        if ($this->config['app_debug'] === 'true' || $this->config['app_debug'] === true) {
+            return $this->getConfig('host_test');
+        }
+        return $this->getConfig('host_pro');
+    }
+
+    public function getDefaultHeaders() {
+        return [
+            'Lkl-Op-Appid: ' . $this->getAppId(),
+            'Lkl-Op-Sdk: lkl-php-sdk-2.0.0',
+            'Lkl-Op-Flowgroup: NORMAL',
+        ];
+    }
+
+    public function readenv($env) {
+        $envData = file_get_contents($env);
+        $lines = explode("\n", $envData);
+    
+        $config = array();
+        foreach ($lines as $line) {
+            $parts = explode('=', $line, 2);
+            if (count($parts) === 2) {
+                $key = strtolower(trim($parts[0]));
+                $value = trim($parts[1]);
+                $config[$key] = $value;
+            }
+        }
+        return $config;
+    }
+    // Getters and setters...
+}

+ 56 - 0
src/OpenAPISDK/V2/V2ObjectSerializer.php

@@ -0,0 +1,56 @@
+<?php
+/**
+ * V2ObjectSerializer
+ * PHP version 7.4
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK\V2
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+
+namespace SixShop\Lakala\OpenAPISDK\V2;
+
+class V2ObjectSerializer
+{
+    public static function sanitizeForSerialization($data)
+    {
+        if (is_array($data)) {
+            foreach ($data as $property => $value) {
+                $data[$property] = self::sanitizeForSerialization($value);
+            }
+            return $data;
+        } elseif (is_object($data)) {
+            $values = [];
+            foreach (get_object_vars($data) as $property => $value) {
+                $values[$property] = self::sanitizeForSerialization($value);
+            }
+            return $values;
+        } else {
+            return $data;
+        }
+    }
+
+    public static function deserialize($data, $class, $headers = null)
+    {
+        if ($class === '\DateTime') {
+            return new \DateTime($data);
+        } elseif (in_array($class, ['bool', 'boolean', 'int', 'integer', 'float', 'double', 'string', 'array'])) {
+            settype($data, $class);
+            return $data;
+        } else {
+            $instance = new $class();
+            foreach ($data as $property => $value) {
+                $camelProp = str_replace(' ', '', ucwords(str_replace('_', ' ', $property)));
+                $setter = 'set' . $camelProp;
+                if (method_exists($instance, $setter)) {
+                    $instance->$setter($value);
+                }
+            }
+            if ($headers && method_exists($instance, 'setHeaders')) {
+                $instance->setHeaders($headers);
+            }
+            return $instance;
+        }
+    }
+}

+ 15 - 0
src/OpenAPISDK/V3/.lklopensdk.env

@@ -0,0 +1,15 @@
+APP_DEBUG = true
+
+[APP]
+APP_ID      = OP00000003
+SERIAL_NO   = 00dfba8194c41b84cf
+# 生产主机地址
+HOST_PRO    = https://s2.lakala.com
+# 测试主机地址
+HOST_TEST   = https://test.wsmsd.cn/sit
+
+[CERT]
+SM4_KEY = uIj6CPg1GZAY10dXFfsEAQ==
+#相对当前配置文件的路径
+MERCHANT_PRIVATE_KEY_PATH   = ../../example/RSAKeys/DEV/OP00000003_private_key.pem
+LKL_CERTIFICATE_PATH        = ../../example/RSAKeys/DEV/lkl-apigw-v2.cer

+ 19 - 0
src/OpenAPISDK/V3/Api/EncryptMode.php

@@ -0,0 +1,19 @@
+<?php
+
+namespace SixShop\Lakala\OpenAPISDK\V3\Api;
+
+/**
+ * EncryptMode Class
+ * 请求或者响应加解密类型
+ */
+class EncryptMode
+{
+    // 普通无加解密:请求为明文,返回也是明文
+    const NONE = 'none';
+    // 只请求加密,返回为明文
+    const REQUEST = 'request';
+    // 请求明文、响应需解密
+    const RESPONSE = 'response';
+    // 请求需加密、返回需解密
+    const BOTH = 'both';
+}

+ 269 - 0
src/OpenAPISDK/V3/Api/LakalaApi.php

@@ -0,0 +1,269 @@
+<?php
+
+namespace SixShop\Lakala\OpenAPISDK\V3\Api;
+
+use SixShop\Lakala\OpenAPISDK\V3\ApiException;
+use SixShop\Lakala\OpenAPISDK\V3\ObjectSerializer;
+use SixShop\Lakala\OpenAPISDK\V3\Configuration;
+use SixShop\Lakala\OpenAPISDK\V3\Util\HttpService;
+use SixShop\Lakala\OpenAPISDK\V3\Util\LakalaSM4;
+
+use SixShop\Lakala\OpenAPISDK\V3\Model\ModelRequest;
+use SixShop\Lakala\OpenAPISDK\V3\Model\ModelResponse;
+
+
+/**
+ * LakalaApi Class
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK\V3\Api
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+class LakalaApi
+{
+
+    /**
+     * @var string 随机字符串集
+     */
+    protected $charset = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+
+    /**
+     * 算法
+     * @var string
+     */
+    protected $schema = "LKLAPI-SHA256withRSA";
+
+    // 请求响应body使用SM4加密类型
+    protected $encryptMode;
+
+    protected $config;
+    protected $httpService;
+
+    protected $resourcePath;
+
+    public function __construct(Configuration $config, $encryptMode = EncryptMode::NONE)
+    {
+        $this->encryptMode = $encryptMode;
+        $this->config = $config;
+        $this->httpService = new \SixShop\Lakala\OpenAPISDK\V3\Util\HttpService();
+    }
+
+    public function setResourcePath($resourcePath)
+    {
+        $this->resourcePath = $resourcePath;
+        return $this;
+    }
+    
+    public function getResourcePath()
+    {
+        return $this->resourcePath;
+    }
+
+    public function tradeApi($resourcePath, ModelRequest $modelRequest,
+                                            $returnType = '\Lakala\OpenAPISDK\V3\Model\ModelResponse',
+                                            $method = 'POST')
+    {
+        $headerParams = [];
+
+        if (!$modelRequest->valid()) {
+            throw new ApiException(implode(',', $modelRequest->listInvalidProperties()));
+        }
+
+        $httpBody = $modelRequest->jsonSerialize();
+        if ($httpBody !== null) {
+            $httpBody = json_encode(ObjectSerializer::sanitizeForSerialization($httpBody), JSON_UNESCAPED_UNICODE);
+        }
+        return $this->apiWithBody($resourcePath, $httpBody, $headerParams, $method, $returnType);
+    }
+
+    public function apiWithBody($resourcePath, $httpBody, $headerParams = [], $method = 'POST',
+                                               $returnType = '\Lakala\OpenAPISDK\V3\Model\ModelResponse')
+    {
+        $this->setResourcePath($resourcePath);
+
+        if (!is_string($httpBody)) {
+            $httpBody = json_encode($httpBody, JSON_UNESCAPED_UNICODE);
+        }
+
+        list($response, $statusCode, $httpHeader) = $this->callApi(
+            $method,
+            $httpBody,
+            $headerParams,
+            $returnType
+        );
+
+        if ($statusCode < 200 || $statusCode > 299) {
+            throw new ApiException(
+                sprintf('[%d] 连接API时出错 (%s)', $statusCode, $this->getResourcePath()),
+                $statusCode,
+                $httpHeader,
+                $response
+            );
+        }
+
+        return $response;
+    }
+
+    protected function callApi($method, $httpBody, $headerParams, $returnType)
+    {
+        $request = $this->prepareRequest($method, $httpBody, $headerParams);
+        try {
+            $options = $this->createRequestOptions();
+            $options['header'] = $request['headers'];
+            $options['data'] = $request['body'];
+            $response = $this->httpService->request($method, $request['url'], $options);
+        } catch (Exception $e) {
+            throw new ApiException("[{$e->getCode()}] {$e->getMessage()}", $e->getCode(), null, null);
+        }
+
+        $statusCode = $response['info']['http_code'];
+        $responseHeaders = isset($response['header']) ? $response['header'] : null;
+
+        if ($statusCode < 200 || $statusCode > 299) {
+            throw new ApiException(
+                sprintf('[%d] 连接API时出错 (%s)', $statusCode, $request['url']),
+                $statusCode,
+                $responseHeaders,
+                $response['body']
+            );
+        }
+
+        $responseVerifySign = $this->responseVerifySign($responseHeaders, $response['body']);
+        if (!$responseVerifySign) {
+            throw new ApiException(
+                sprintf('[%d] 验证拉卡拉响应验签错误 (%s)', $statusCode, $request['url']),
+                $statusCode,
+                $responseHeaders,
+                $response['body']
+            );
+        }
+        // 请求咱解密
+        if($this->encryptMode == EncryptMode::RESPONSE || $this->encryptMode == EncryptMode::BOTH) {
+            // echo "\n<!-- \n" . $response['body'] . "\n--->\n";
+            $sm4 = new LakalaSM4();
+            $body = $sm4->decrypt(base64_decode($this->config->getSm4Key()), $response['body']);
+            $response['content'] = json_decode($body);
+            $response['body'] = $body;
+        }
+        $response['content']->originalText = $response['body'];
+
+        return [
+            ObjectSerializer::deserialize($response['content'], $returnType, $responseHeaders),
+            $statusCode,
+            $responseHeaders
+        ];
+    }
+
+    protected function prepareRequest($method, $httpBody, $headerParams)
+    {
+        $url = $this->config->getHost() . $this->getResourcePath();
+
+        // SM4加密请求体
+        if($this->encryptMode == EncryptMode::REQUEST || $this->encryptMode == EncryptMode::BOTH) {
+            $sm4 = new LakalaSM4();
+            $httpBody = $sm4->encrypt(base64_decode($this->config->getSm4Key()), $httpBody);
+        }
+
+        $headers = $this->createHeaderParams($headerParams, $httpBody);
+
+        return [
+            'method' => $method,
+            'url' => $url,
+            'headers' => $headers,
+            'body' => $httpBody,
+        ];
+    }
+
+    protected function createHeaderParams($headerParams, $httpBody)
+    {
+        $headers = $this->config->getDefaultHeaders();
+
+        if ($headerParams) {
+            $headers = array_merge($headers, $headerParams);
+        }
+
+        $authorization = $this->getAuthorization($httpBody);
+        $headers[] = 'Content-Type: application/json';
+        $headers[] = "Authorization: $authorization";
+
+        return $headers;
+    }
+
+    protected function createRequestOptions()
+    {
+        $options = [
+            'timeout' => 10,
+            'respond_type' => \SixShop\Lakala\OpenAPISDK\V3\Util\HttpService::RESPOND_TYPE_ARRAY,
+        ];
+
+        return $options;
+    }
+
+    protected function getAuthorization($body)
+    {
+        $randomString = $this->getRandomString(12);
+        $timestamp = time();
+        $data = $this->config->getAppId() . "\n"
+                . $this->config->getSerialNo() . "\n"
+                . $timestamp . "\n"
+                . $randomString . "\n"
+                . $body . "\n";
+
+        $sign = $this->rsaSign($data);
+
+        $authorization = $this->schema . " appid=\"" . $this->config->getAppId() . "\","
+                        . "serial_no=\"" . $this->config->getSerialNo() . "\","
+                        . "timestamp=\"" . $timestamp . "\","
+                        . "nonce_str=\"" . $randomString . "\","
+                        . "signature=\"" . $sign . "\"";
+
+        return $authorization;
+    }
+
+    protected function getRandomString($length = 10) {
+        $randomString = '';
+        $charsetLength = strlen($this->charset);
+    
+        // 生成随机字符串
+        for ($i = 0; $i < $length; $i++) {
+            $randomChar = $this->charset[rand(0, $charsetLength - 1)];  // 随机选择字符
+            $randomString .= $randomChar;
+        }
+    
+        return $randomString;
+    }
+
+    //生成 sha256WithRSA 签名
+    protected function rsaSign($content) {
+        $privateContent = file_get_contents($this->config->getMerchantPrivateKeyPath());
+        $privateKey = openssl_pkey_get_private($privateContent);
+        if (!$privateKey) {
+            throw new ApiException('获取私钥失败');
+        }
+        $res = openssl_sign($content, $sign, $privateKey, OPENSSL_ALGO_SHA256);
+        if (function_exists('openssl_free_key') && version_compare(PHP_VERSION, '8.0.0', '<')) {
+            openssl_free_key($privateKey);
+        }
+        if (!$res) {
+            throw new ApiException('[10004] 拉卡拉字符串签名失败');
+        }
+        return base64_encode($sign);
+    }
+
+    protected function responseVerifySign($headers, $body) {
+        $sign = $headers['Lklapi-Signature'];
+        $sign = base64_decode($sign);
+        $data = $headers['Lklapi-Appid'] . "\n"
+                . $headers['Lklapi-Serial'] . "\n"
+                . $headers['Lklapi-Timestamp'] . "\n"
+                . $headers['Lklapi-Nonce'] . "\n"
+                . $body . "\n";
+        // $dir = dirname(__FILE__);
+        // $dir = str_replace('/src/Api', '', $dir);
+        $certContent = file_get_contents($this->config->getLklCertificatePath());
+        $key = openssl_pkey_get_public($certContent);
+        $result = openssl_verify($data, $sign, $key, OPENSSL_ALGO_SHA256) === 1;
+        return $result;
+    }
+}

+ 76 - 0
src/OpenAPISDK/V3/Api/LakalaNotifyApi.php

@@ -0,0 +1,76 @@
+<?php
+
+namespace SixShop\Lakala\OpenAPISDK\V3\Api;
+
+use SixShop\Lakala\OpenAPISDK\V3\ApiException;
+use SixShop\Lakala\OpenAPISDK\V3\Configuration;
+use SixShop\Lakala\OpenAPISDK\V3\ObjectSerializer;
+
+use SixShop\Lakala\OpenAPISDK\V3\Model\ModelTradeNotify;
+
+/**
+ * LakalaNotifyApi Class
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK\V3\Api
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+class LakalaNotifyApi
+{
+    protected $config;
+
+    public function __construct(Configuration $config)
+    {
+        $this->config = $config;
+    }
+
+    public function notiApi() {
+        $headers = getallheaders();
+        $body = file_get_contents('php://input');
+
+        $verify = $this->requestVerifySign($headers['Authorization'], $body);
+
+        if (!$verify) {
+            throw new ApiException('验证拉卡拉验签错误');
+        }
+
+        $notify = new \SixShop\Lakala\OpenAPISDK\V3\Model\ModelTradeNotify();
+        $notify->setHeaders($headers);
+        $notify->setOriginalText($body);
+
+        return $notify;
+    }
+
+    protected function requestVerifySign($authorization, $body) {
+        $pattern = '/timestamp="(\d+)",nonce_str="(\w+)",signature="([^"]+)"/';
+        preg_match($pattern, $authorization, $matches);
+        if (count($matches) == 0) return false;
+
+        $timestamp = $matches[1];
+        $nonce_str = $matches[2];
+        $signature = $matches[3];
+
+        $sign = base64_decode($signature);
+        $data = $timestamp. "\n"
+                . $nonce_str . "\n"
+                . $body . "\n";
+
+        $certContent = file_get_contents($this->config->getLklCertificatePath());
+        $key = openssl_pkey_get_public($certContent);
+        $result = openssl_verify($data, $sign, $key, OPENSSL_ALGO_SHA256) === 1;
+        return $result;
+    }
+
+    public function success() {
+        echo '{"code":"SUCCESS","message":"执行成功"}';
+    }
+
+    public function fail($msg) {
+        $ret = [
+            'code' => 'FAIL',
+            'message' => $msg
+        ];
+        echo json_encode($ret, JSON_UNESCAPED_UNICODE);
+    }
+}

+ 26 - 0
src/OpenAPISDK/V3/Api/QueryTradequeryApi.php

@@ -0,0 +1,26 @@
+<?php
+/**
+ * 查询交易
+ * QueryTradequeryApi
+ * PHP version 7.4
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+
+namespace SixShop\Lakala\OpenAPISDK\V3\Api;
+
+use SixShop\Lakala\OpenAPISDK\V3\Model\QueryTradequeryRequest;
+use SixShop\Lakala\OpenAPISDK\V3\Model\QueryTradequeryResponse;
+
+class QueryTradequeryApi extends LakalaApi
+{
+    public function queryTradequery(QueryTradequeryRequest $queryTradequeryRequest)
+    {
+        $resourcePath = '/api/v3/labs/query/tradequery';
+
+        return $this->tradeApi($resourcePath, $queryTradequeryRequest, '\Lakala\OpenAPISDK\V3\Model\QueryTradequeryResponse');
+    }
+}

+ 25 - 0
src/OpenAPISDK/V3/Api/RelationRefundApi.php

@@ -0,0 +1,25 @@
+<?php
+/**
+ * 退款交易
+ * RelationRefundApi
+ * PHP version 7.4
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+
+namespace SixShop\Lakala\OpenAPISDK\V3\Api;
+
+use SixShop\Lakala\OpenAPISDK\V3\Model\RelationRefundRequest;
+use SixShop\Lakala\OpenAPISDK\V3\Model\RelationRefundResponse;
+
+class RelationRefundApi extends LakalaApi
+{
+    public function relationRefund(RelationRefundRequest $relationRefundRequest)
+    {
+        $resourcePath = '/api/v3/labs/relation/refund';
+        return $this->tradeApi($resourcePath, $relationRefundRequest, '\Lakala\OpenAPISDK\V3\Model\RelationRefundResponse');
+    }
+}

+ 28 - 0
src/OpenAPISDK/V3/Api/TransMicropayApi.php

@@ -0,0 +1,28 @@
+<?php
+/**
+ * 被扫交易
+ * TransMicropayApi
+ * PHP version 7.4
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+
+namespace SixShop\Lakala\OpenAPISDK\V3\Api;
+
+use SixShop\Lakala\OpenAPISDK\V3\Model\TransMicropayRequest;
+use SixShop\Lakala\OpenAPISDK\V3\Model\TransMicropayResponse;
+
+class TransMicropayApi extends LakalaApi
+{
+    public function transMicropay(TransMicropayRequest $transMicropayRequest)
+    {
+        $resourcePath = $this->encryptMode == EncryptMode::REQUEST || $this->encryptMode == EncryptMode::BOTH
+                      ? '/api/v3/labs/trans/micropay_encry'
+                      : '/api/v3/labs/trans/micropay';
+
+        return $this->tradeApi($resourcePath, $transMicropayRequest, '\Lakala\OpenAPISDK\V3\Model\TransMicropayResponse');
+    }
+}

+ 29 - 0
src/OpenAPISDK/V3/Api/TransPreorderApi.php

@@ -0,0 +1,29 @@
+<?php
+/**
+ * 主扫交易
+ * TransPreorderApi
+ * PHP version 7.4
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+
+namespace SixShop\Lakala\OpenAPISDK\V3\Api;
+
+use SixShop\Lakala\OpenAPISDK\V3\Model\TransPreorderRequest;
+use SixShop\Lakala\OpenAPISDK\V3\Model\TransPreorderResponse;
+
+class TransPreorderApi extends LakalaApi
+{
+
+    public function transPreorder(TransPreorderRequest $transPreorderRequest)
+    {
+        $resourcePath = $this->encryptMode == EncryptMode::REQUEST || $this->encryptMode == EncryptMode::BOTH 
+                      ? '/api/v3/labs/trans/preorder_encry'
+                      : '/api/v3/labs/trans/preorder';
+
+        return $this->tradeApi($resourcePath, $transPreorderRequest, '\Lakala\OpenAPISDK\V3\Model\TransPreorderResponse');
+    }
+}

+ 37 - 0
src/OpenAPISDK/V3/ApiException.php

@@ -0,0 +1,37 @@
+<?php
+/**
+ * ApiException
+ * PHP version 7.4
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+
+namespace SixShop\Lakala\OpenAPISDK\V3;
+
+use Exception;
+
+class ApiException extends Exception
+{
+    protected $responseBody;
+    protected $responseHeaders;
+
+    public function __construct($message = "", $code = 0, $responseBody = null, $responseHeaders = null)
+    {
+        parent::__construct($message, $code);
+        $this->responseBody = $responseBody;
+        $this->responseHeaders = $responseHeaders;
+    }
+
+    public function getResponseBody()
+    {
+        return $this->responseBody;
+    }
+
+    public function getResponseHeaders()
+    {
+        return $this->responseHeaders;
+    }
+}

+ 87 - 0
src/OpenAPISDK/V3/Configuration.php

@@ -0,0 +1,87 @@
+<?php
+/**
+ * Configuration
+ * PHP version 7.4
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+
+namespace SixShop\Lakala\OpenAPISDK\V3;
+
+class Configuration
+{
+    private $config;
+
+    public function __construct(string | array $config = '.lklopensdk.env')
+    {
+        if (is_string($config)) {
+            $dir = dirname(__FILE__);
+            $this->config = $this->readenv($dir . '/' . $config);
+            $this->config['app_debug'] = strtolower($this->config['app_debug']);
+            $this->config['merchant_private_key_path'] = realpath($dir . '/' . $this->config['merchant_private_key_path']);
+            $this->config['lkl_certificate_path'] = realpath($dir . '/' . $this->config['lkl_certificate_path']);
+        }
+        else if (is_array($config)) {
+            $this->config = $config;
+        }
+    }
+
+    public function getAppId() {
+        return $this->getConfig('app_id');
+    }
+
+    public function getSerialNo() {
+        return $this->getConfig('serial_no');
+    }
+
+    public function getSm4Key() {
+        return $this->getConfig('sm4_key');
+    }
+
+    public function getMerchantPrivateKeyPath() {
+        return $this->getConfig('merchant_private_key_path');
+    }
+
+    public function getLklCertificatePath() {
+        return $this->getConfig('lkl_certificate_path');
+    }
+
+    private function getConfig($key) {
+        return $this->config[$key];
+    }
+
+    public function getHost() {
+        if ($this->config['app_debug'] === 'true' || $this->config['app_debug'] === true) {
+            return $this->getConfig('host_test');
+        }
+        return $this->getConfig('host_pro');
+    }
+
+    public function getDefaultHeaders() {
+        return [
+            'Lkl-Op-Appid: ' . $this->getAppId(),
+            'Lkl-Op-Sdk: lkl-php-sdk-3.0.0',
+            'Lkl-Op-Flowgroup: NORMAL',
+        ];
+    }
+
+    public function readenv($env) {
+        $envData = file_get_contents($env);
+        $lines = explode("\n", $envData);
+    
+        $config = array();
+        foreach ($lines as $line) {
+            $parts = explode('=', $line, 2);
+            if (count($parts) === 2) {
+                $key = strtolower(trim($parts[0]));
+                $value = trim($parts[1]);
+                $config[$key] = $value;
+            }
+        }
+        return $config;
+    }
+    // Getters and setters...
+}

+ 94 - 0
src/OpenAPISDK/V3/Model/ModelRequest.php

@@ -0,0 +1,94 @@
+<?php
+/**
+ * ModelRequest
+ * PHP version 7.4
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK\V3\Model
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+
+namespace SixShop\Lakala\OpenAPISDK\V3\Model;
+
+class ModelRequest {
+    // 请求时间	M	String(14)	请求时间,格式yyyyMMddHHmmss
+    protected $reqTime;
+    // 版本号	M	String(8)	3.0
+    protected $version;
+    // 请求参数	M	Object	参见各个接口的请求参数格式
+    protected $reqData;
+
+    public function __construct()
+    {
+        $this->version = '3.0';
+        $this->reqTime = date('YmdHis', time());
+        $this->reqData = [];
+    }
+
+    public function setReqTime($reqTime)
+    {
+        $this->reqTime = $reqTime;
+        return $this;
+    }
+    
+    public function getReqTime()
+    {
+        return $this->reqTime;
+    }
+    
+    public function setVersion($version)
+    {
+        $this->version = $version;
+        return $this;
+    }
+    
+    public function getVersion()
+    {
+        return $this->version;
+    }
+
+    public function setReqData($reqData)
+    {
+        $this->reqData = $reqData;
+        return $this;
+    }
+    
+    public function getReqData()
+    {
+        return $this->reqData;
+    }
+
+    /**
+     * Show all the invalid properties with reasons.
+     *
+     * @return array invalid properties with reasons
+     */
+    public function listInvalidProperties()
+    {
+        $invalidProperties = [];
+
+        return $invalidProperties;
+    }
+
+    /**
+     * Validate all the properties in the model
+     * return true if all passed
+     *
+     * @return bool True if all properties are valid
+     */
+    public function valid()
+    {
+        return count($this->listInvalidProperties()) === 0;
+    }
+
+    #[\ReturnTypeWillChange]
+    public function jsonSerialize(): mixed
+    {
+        return [
+            'req_time' => $this->getReqTime(),
+            'version' => $this->getVersion(),
+            'req_data' => $this->getReqData()
+        ];
+    }
+}

+ 114 - 0
src/OpenAPISDK/V3/Model/ModelResponse.php

@@ -0,0 +1,114 @@
+<?php
+/**
+ * ModelResponse
+ * PHP version 7.4
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK\V3\Model
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+
+namespace SixShop\Lakala\OpenAPISDK\V3\Model;
+
+class ModelResponse {
+    // 返回业务代码	M	String(8)	返回业务代码(000000为成功,其余按照错误信息来定)
+    /**
+     * 交易码	 交易码描述
+     * BBS00000	成功
+     * BBS10000	交易状态为支付中
+     * 000100	网络请求失败
+     * 000101	网络请求超时
+     * 其它	失败
+     */
+    protected $code;
+    // 返回业务代码描述	M	String(64)	返回业务代码描述
+    protected $msg;
+    // 请求时间	M	String(14)	请求时间,格式yyyyMMddHHmmss
+    protected $respTime;
+    // 响应参数	C	Object	返回数据.下文定义的响应均为该属性中的内容
+    protected $respData;
+    // 响应头信息
+    protected $headers;
+    // 响应原文
+    protected $originalText;
+
+    public function setCode($code)
+    {
+        $this->code = $code;
+        return $this;
+    }
+
+    public function getCode()
+    {
+        return $this->code;
+    }
+
+    public function setMsg($msg)
+    {
+        $this->msg = $msg;
+        return $this;
+    }
+
+    public function getMsg()
+    {
+        return $this->msg;
+    }
+
+    public function setRespTime($respTime)
+    {
+        $this->respTime = $respTime;
+        return $this;
+    }
+
+    public function getRespTime()
+    {
+        return $this->respTime;
+    }
+
+    public function setRespData($respData)
+    {
+        $this->respData = $respData;
+
+        if ($respData !== null) {
+            foreach ($respData as $property => $value) {
+                $camelProp = str_replace(' ', '', ucwords(str_replace('_', ' ', $property)));
+                $setter = 'set' . $camelProp;
+                if (method_exists($this, $setter)) {
+                    $this->$setter($value);
+                }
+            }
+        }
+        return $this;
+    }
+    
+    public function getRespData()
+    {
+        return $this->respData;
+    }
+
+    public function setHeaders($headers)
+    {
+        $this->headers = $headers;
+        return $this;
+    }
+    
+    public function getHeaders()
+    {
+        return $this->headers;
+    }
+
+    public function setOriginalText($originalText) {
+        $this->originalText = $originalText;
+        return $this;
+    }
+
+    public function getOriginalText() {
+        return $this->originalText;
+    }
+
+    public function jsonSerialize()
+    {
+       return ObjectSerializer::sanitizeForSerialization($this);
+    }
+}

+ 46 - 0
src/OpenAPISDK/V3/Model/ModelTradeNotify.php

@@ -0,0 +1,46 @@
+<?php
+/**
+ * ModelTradeNotify
+ * 交易回调Model
+ * 
+ * PHP version 7.4
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK\V3\Model
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+
+namespace SixShop\Lakala\OpenAPISDK\V3\Model;
+
+class ModelTradeNotify {
+    // 响应头信息
+    protected $headers;
+    // 响应原文
+    protected $originalText;
+
+    public function setHeaders($headers)
+    {
+        $this->headers = $headers;
+        return $this;
+    }
+    
+    public function getHeaders()
+    {
+        return $this->headers;
+    }
+
+    public function setOriginalText($originalText) {
+        $this->originalText = $originalText;
+        return $this;
+    }
+
+    public function getOriginalText() {
+        return $this->originalText;
+    }
+
+    public function jsonSerialize()
+    {
+       return ObjectSerializer::sanitizeForSerialization($this);
+    }
+}

+ 95 - 0
src/OpenAPISDK/V3/Model/QueryTradequeryRequest.php

@@ -0,0 +1,95 @@
+<?php
+/**
+ * 查询交易 请求字段
+ * QueryTradequeryRequest
+ * PHP version 7.4
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK\V3\Model
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+
+namespace SixShop\Lakala\OpenAPISDK\V3\Model;
+
+class QueryTradequeryRequest extends ModelRequest implements \JsonSerializable
+{
+    // 商户号	M	String(32)	拉卡拉分配的商户号
+    protected $merchantNo;
+    // 终端号	M	String(32)	拉卡拉分配的业务终端号
+    protected $termNo;
+    // 商户交易流水号	C	String(32) 下单时的商户请求流水号 说明:out_trade_no、trade_no、必有其一。如果存在多个字段上送,优先级顺序如下: trade_no、 out_trade_no
+    protected $outTradeNo;
+    // 拉卡拉交易流水号	C	String(32) 拉卡拉交易流水号
+    protected $tradeNo;
+    
+    public function setMerchantNo($merchantNo)
+    {
+        $this->merchantNo = $merchantNo;
+        return $this;
+    }
+    
+    public function getMerchantNo()
+    {
+        return $this->merchantNo;
+    }
+    
+    public function setTermNo($termNo)
+    {
+        $this->termNo = $termNo;
+        return $this;
+    }
+    
+    public function getTermNo()
+    {
+        return $this->termNo;
+    }
+    
+    public function setOutTradeNo($outTradeNo)
+    {
+        $this->outTradeNo = $outTradeNo;
+        return $this;
+    }
+    
+    public function getOutTradeNo()
+    {
+        return $this->outTradeNo;
+    }
+    
+    public function setTradeNo($tradeNo)
+    {
+        $this->tradeNo = $tradeNo;
+        return $this;
+    }
+    
+    public function getTradeNo()
+    {
+        return $this->tradeNo;
+    }
+
+    /**
+     * Show all the invalid properties with reasons.
+     *
+     * @return array invalid properties with reasons
+     */
+    public function listInvalidProperties()
+    {
+        $invalidProperties = [];
+        if (strlen($this->merchantNo)===0) $invalidProperties[] = '商户号不能为空';
+        if (strlen($this->termNo)===0) $invalidProperties[] = '终端号不能为空';
+        if (strlen($this->outTradeNo)===0 && strlen($this->tradeNo)===0) $invalidProperties[] = '商户交易流水号、拉卡拉交易流水号至少一项不能为空';
+
+        return $invalidProperties;
+    }
+
+    public function jsonSerialize()
+    {
+        $this->setReqData([
+            'merchant_no' => $this->merchantNo,
+            'term_no' => $this->termNo,
+            'out_trade_no' => $this->outTradeNo,
+            'trade_no' => $this->tradeNo,
+        ]);
+        return parent::jsonSerialize();
+    }
+}

+ 341 - 0
src/OpenAPISDK/V3/Model/QueryTradequeryResponse.php

@@ -0,0 +1,341 @@
+<?php
+/**
+ * QueryTradequeryRequest
+ * PHP version 7.4
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK\V3\Model
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+
+namespace SixShop\Lakala\OpenAPISDK\V3\Model;
+
+class QueryTradequeryResponse extends ModelResponse implements \JsonSerializable
+{
+    // 商户号	M	String(32)	拉卡拉分配的商户号(请求接口中商户号)
+    protected $merchantNo;
+    // 商户请求流水号	M	String(32)	请求中的商户请求流水号
+    protected $outTradeNo;
+    // 拉卡拉商户订单号	M	String(32)	拉卡拉生成的交易流水
+    protected $tradeNo;
+    // 	拉卡拉对账单流水号	M	String(14)	trade_no的后14位
+    protected $logNo;
+    // 交易大类	C	String(32)	PREORDER-主扫,MICROPAY-被扫,REFUND-退款,CANCEL-撤销,无-其它类型
+    protected $tradeMainType;
+    // 拆单属性	C	String(1)	只有涉及合单交易时会出现:M-主单,S-子单
+    protected $splitAttr;
+    // 拆单信息	C	List<>	如果查询订单是主单,则返回。见splitInfo字段说明。拆单信息见split_info域说明
+    protected $splitInfo;
+    // 账户端交易订单号	M	String(48)	账户端交易流水号
+    protected $accTradeNo;
+    // 钱包类型	M	String(32)	微信:WECHAT 支付宝:ALIPAY 银联:UQRCODEPAY 翼支付: BESTPAY 苏宁易付宝: SUNING
+    protected $accountType;
+    // 交易状态	M	String(16)	INIT-初始化 CREATE-下单成功 SUCCESS-交易成功 FAIL-交易失败 DEAL-交易处理中 UNKNOWN-未知状态 CLOSE-订单关闭 PART_REFUND-部分退款 REFUND-全部退款(或订单被撤销)交易状态	M	String(16)	INIT-初始化 CREATE-下单成功 SUCCESS-交易成功 FAIL-交易失败 DEAL-交易处理中 UNKNOWN-未知状态 CLOSE-订单关闭 PART_REFUND-部分退款 REFUND-全部退款(或订单被撤销)
+    protected $tradeState;
+    // 	交易状态描述	C	String(256)	交易状态描述
+    protected $tradeStateDesc;
+    // 订单金额	M	String(12)	单位分,整数数字型字符
+    protected $totalAmount;
+    // 付款人实付金额	C	String(12)	付款人实付金额,单位分
+    protected $payerAmount;
+    // 账户端结算金额	C	String(12)	账户端应结订单金额,单位分
+    protected $accSettleAmount;
+    // 商户侧优惠金额(账户端)	C	String(12)	商户优惠金额,单位分
+    protected $accMdiscountAmount;
+    // 账户端优惠金额	C	String(12)	拉卡拉优惠金额,单位分
+    protected $accDiscountAmount;
+    // 账户端其它优惠金额	C	String(12)	
+    protected $accOtherDiscountAmount;
+    // 交易完成时间	C	String(14)	实际支付时间。yyyyMMddHHmmss
+    protected $tradeTime;
+    // 用户标识1	C	String(128)	微信sub_open_id 支付宝buyer_logon_id(买家支付宝账号)
+    protected $userId1;
+    // 用户标识2	C	String(128)	微信openId支 付宝buyer_user_id 银联user_id
+    protected $userId2;
+    // 付款银行	C	String(128)	付款银行
+    protected $bankType;
+    // 银行卡类型	C	String(16)	00:借记 01:贷记 02:微信零钱 03:支付宝花呗 04:支付宝其他 05:数字货币 06:拉卡拉支付账户 99:未知
+    protected $cardType;
+    // 活动 ID	C	String(32)	在账户端商户后台配置的批次 ID
+    protected $accActivityId;
+    // 账户端返回信息域	C	Object	账户端返回信息域
+    protected $accRespFields;
+    // 合单退款拆单信息	C	List<>	如果查询订单是退款主单,则返回。见refundSplitInfo字段说明。拆单信息见refund_split_info域说明
+    protected $refundSplitInfo;
+    
+    public function setMerchantNo($merchantNo)
+    {
+        $this->merchantNo = $merchantNo;
+        return $this;
+    }
+    
+    public function getMerchantNo()
+    {
+        return $this->merchantNo;
+    }
+    
+    public function setOutTradeNo($outTradeNo)
+    {
+        $this->outTradeNo = $outTradeNo;
+        return $this;
+    }
+    
+    public function getOutTradeNo()
+    {
+        return $this->outTradeNo;
+    }
+    
+    public function setTradeNo($tradeNo)
+    {
+        $this->tradeNo = $tradeNo;
+        return $this;
+    }
+    
+    public function getTradeNo()
+    {
+        return $this->tradeNo;
+    }
+    
+    public function setLogNo($logNo)
+    {
+        $this->logNo = $logNo;
+        return $this;
+    }
+    
+    public function getLogNo()
+    {
+        return $this->logNo;
+    }
+    
+    public function setTradeMainType($tradeMainType)
+    {
+        $this->tradeMainType = $tradeMainType;
+        return $this;
+    }
+    
+    public function getTradeMainType()
+    {
+        return $this->tradeMainType;
+    }
+    
+    public function setSplitAttr($splitAttr)
+    {
+        $this->splitAttr = $splitAttr;
+        return $this;
+    }
+    
+    public function getSplitAttr()
+    {
+        return $this->splitAttr;
+    }
+    
+    public function setSplitInfo($splitInfo)
+    {
+        $this->splitInfo = $splitInfo;
+        return $this;
+    }
+    
+    public function getSplitInfo()
+    {
+        return $this->splitInfo;
+    }
+    
+    public function setAccTradeNo($accTradeNo)
+    {
+        $this->accTradeNo = $accTradeNo;
+        return $this;
+    }
+    
+    public function getAccTradeNo()
+    {
+        return $this->accTradeNo;
+    }
+    
+    public function setAccountType($accountType)
+    {
+        $this->accountType = $accountType;
+        return $this;
+    }
+    
+    public function getAccountType()
+    {
+        return $this->accountType;
+    }
+    
+    public function setTradeState($tradeState)
+    {
+        $this->tradeState = $tradeState;
+        return $this;
+    }
+    
+    public function getTradeState()
+    {
+        return $this->tradeState;
+    }
+    
+    public function setTradeStateDesc($tradeStateDesc)
+    {
+        $this->tradeStateDesc = $tradeStateDesc;
+        return $this;
+    }
+    
+    public function getTradeStateDesc()
+    {
+        return $this->tradeStateDesc;
+    }
+    
+    public function setTotalAmount($totalAmount)
+    {
+        $this->totalAmount = $totalAmount;
+        return $this;
+    }
+    
+    public function getTotalAmount()
+    {
+        return $this->totalAmount;
+    }
+    
+    public function setPayerAmount($payerAmount)
+    {
+        $this->payerAmount = $payerAmount;
+        return $this;
+    }
+    
+    public function getPayerAmount()
+    {
+        return $this->payerAmount;
+    }
+    
+    public function setAccSettleAmount($accSettleAmount)
+    {
+        $this->accSettleAmount = $accSettleAmount;
+        return $this;
+    }
+    
+    public function getAccSettleAmount()
+    {
+        return $this->accSettleAmount;
+    }
+    
+    public function setAccMdiscountAmount($accMdiscountAmount)
+    {
+        $this->accMdiscountAmount = $accMdiscountAmount;
+        return $this;
+    }
+    
+    public function getAccMdiscountAmount()
+    {
+        return $this->accMdiscountAmount;
+    }
+    
+    public function setAccDiscountAmount($accDiscountAmount)
+    {
+        $this->accDiscountAmount = $accDiscountAmount;
+        return $this;
+    }
+    
+    public function getAccDiscountAmount()
+    {
+        return $this->accDiscountAmount;
+    }
+    
+    public function setAccOtherDiscountAmount($accOtherDiscountAmount)
+    {
+        $this->accOtherDiscountAmount = $accOtherDiscountAmount;
+        return $this;
+    }
+    
+    public function getAccOtherDiscountAmount()
+    {
+        return $this->accOtherDiscountAmount;
+    }
+    
+    public function setTradeTime($tradeTime)
+    {
+        $this->tradeTime = $tradeTime;
+        return $this;
+    }
+    
+    public function getTradeTime()
+    {
+        return $this->tradeTime;
+    }
+    
+    public function setUserId1($userId1)
+    {
+        $this->userId1 = $userId1;
+        return $this;
+    }
+    
+    public function getUserId1()
+    {
+        return $this->userId1;
+    }
+    
+    public function setUserId2($userId2)
+    {
+        $this->userId2 = $userId2;
+        return $this;
+    }
+    
+    public function getUserId2()
+    {
+        return $this->userId2;
+    }
+    
+    public function setBankType($bankType)
+    {
+        $this->bankType = $bankType;
+        return $this;
+    }
+    
+    public function getBankType()
+    {
+        return $this->bankType;
+    }
+    
+    public function setCardType($cardType)
+    {
+        $this->cardType = $cardType;
+        return $this;
+    }
+    
+    public function getCardType()
+    {
+        return $this->cardType;
+    }
+    
+    public function setAccActivityId($accActivityId)
+    {
+        $this->accActivityId = $accActivityId;
+        return $this;
+    }
+    
+    public function getAccActivityId()
+    {
+        return $this->accActivityId;
+    }
+    
+    public function setAccRespFields($accRespFields)
+    {
+        $this->accRespFields = $accRespFields;
+        return $this;
+    }
+    
+    public function getAccRespFields()
+    {
+        return $this->accRespFields;
+    }
+    
+    public function setRefundSplitInfo($refundSplitInfo)
+    {
+        $this->refundSplitInfo = $refundSplitInfo;
+        return $this;
+    }
+    
+    public function getRefundSplitInfo()
+    {
+        return $this->refundSplitInfo;
+    }
+}

+ 168 - 0
src/OpenAPISDK/V3/Model/RelationRefundRequest.php

@@ -0,0 +1,168 @@
+<?php
+/**
+ * 退款交易 请求字段
+ * RelationRefundRequest
+ * PHP version 7.4
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK\V3\Model
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+
+namespace SixShop\Lakala\OpenAPISDK\V3\Model;
+
+class RelationRefundRequest extends ModelRequest implements \JsonSerializable
+{
+    // 商户号	M	String(32)	拉卡拉分配的商户号
+    protected $merchantNo;
+    // 终端号	M	String(32)	拉卡拉分配的商户号
+    protected $termNo;
+    // 商户交易流水号	M	String(32)	商户系统唯一
+    protected $outTradeNo;
+    // 退款金额	M	String(12)	单位分,整数数字型字符
+    protected $refundAmount;
+    // 退款原因	C	String(32)	退款原因描述
+    protected $refundReason;
+    // 原商户交易流水号	C	String(32)	下单时的商户请求流水号(退款时origin_out_trade_no,origin_trade_no,origin_log_no必送其一)
+    protected $originOutTradeNo;
+    // 原拉卡拉交易流水号	C	String(32)	下单成功时,返回的拉卡拉交易流水。 origin_out_trade_no、origin_log_no、origin_trade_no至少一个必填(调用收银台下单接口拉起交易后发起退款时至少要传两个),同时存在时优先级顺序如下: origin_trade_no、origin_log_no、origin_out_trade_no。
+    protected $originTradeNo;
+    // 原对账单流水号	C	String(14)	对账单中的交易流水。 origin_out_trade_no、origin_log_no、origin_trade_no至少一个必填(调用收银台下单接口拉起交易后发起退款时至少要传两个,同时存在时优先级顺序如下: origin_trade_no、origin_log_no、origin_out_trade_no。
+    protected $originLogNo;
+    // 地址位置信息	M	Object	地址位置信息,风控要求必送
+    protected $locationInfo;
+    
+    public function setMerchantNo($merchantNo)
+    {
+        $this->merchantNo = $merchantNo;
+        return $this;
+    }
+    
+    public function getMerchantNo()
+    {
+        return $this->merchantNo;
+    }
+    
+    public function setTermNo($termNo)
+    {
+        $this->termNo = $termNo;
+        return $this;
+    }
+    
+    public function getTermNo()
+    {
+        return $this->termNo;
+    }
+    
+    public function setOutTradeNo($outTradeNo)
+    {
+        $this->outTradeNo = $outTradeNo;
+        return $this;
+    }
+    
+    public function getOutTradeNo()
+    {
+        return $this->outTradeNo;
+    }
+    
+    public function setRefundAmount($refundAmount)
+    {
+        $this->refundAmount = $refundAmount;
+        return $this;
+    }
+    
+    public function getRefundAmount()
+    {
+        return $this->refundAmount;
+    }
+    
+    public function setRefundReason($refundReason)
+    {
+        $this->refundReason = $refundReason;
+        return $this;
+    }
+    
+    public function getRefundReason()
+    {
+        return $this->refundReason;
+    }
+    
+    public function setOriginOutTradeNo($originOutTradeNo)
+    {
+        $this->originOutTradeNo = $originOutTradeNo;
+        return $this;
+    }
+    
+    public function getOriginOutTradeNo()
+    {
+        return $this->originOutTradeNo;
+    }
+    
+    public function setOriginTradeNo($originTradeNo)
+    {
+        $this->originTradeNo = $originTradeNo;
+        return $this;
+    }
+    
+    public function getOriginTradeNo()
+    {
+        return $this->originTradeNo;
+    }
+    
+    public function setOriginLogNo($originLogNo)
+    {
+        $this->originLogNo = $originLogNo;
+        return $this;
+    }
+    
+    public function getOriginLogNo()
+    {
+        return $this->originLogNo;
+    }
+    
+    public function setLocationInfo($locationInfo)
+    {
+        $this->locationInfo = $locationInfo;
+        return $this;
+    }
+    
+    public function getLocationInfo()
+    {
+        return $this->locationInfo;
+    }
+
+    /**
+     * Show all the invalid properties with reasons.
+     *
+     * @return array invalid properties with reasons
+     */
+    public function listInvalidProperties()
+    {
+        $invalidProperties = [];
+        if (strlen($this->merchantNo)===0) $invalidProperties[] = '商户号不能为空';
+        if (strlen($this->termNo)===0) $invalidProperties[] = '终端号不能为空';
+        if (strlen($this->outTradeNo)===0) $invalidProperties[] = '商户交易流水号不能为空';
+        if (strlen($this->refundAmount)===0) $invalidProperties[] = '退款金额不能为空';
+        if ($this->locationInfo == null) $invalidProperties[] = '地址位置信息不能为空';
+
+        return $invalidProperties;
+    }
+
+    public function jsonSerialize()
+    {
+        $this->setReqData([
+            'merchant_no' => $this->merchantNo,
+            'term_no' => $this->termNo,
+            'out_trade_no' => $this->outTradeNo,
+            'refund_amount' => $this->refundAmount,
+            'refund_reason' => $this->refundReason,
+            'origin_out_trade_no' => $this->originOutTradeNo,
+            'origin_trade_no' => $this->originTradeNo,
+            'origin_log_no' => $this->originLogNo,
+            'location_info' => $this->locationInfo === null ? $this->locationInfo : $this->locationInfo->jsonSerialize(),
+
+        ]);
+        return parent::jsonSerialize();
+    }
+}

+ 211 - 0
src/OpenAPISDK/V3/Model/RelationRefundResponse.php

@@ -0,0 +1,211 @@
+<?php
+/**
+ * RelationRefundRequest
+ * PHP version 7.4
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK\V3\Model
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+
+namespace SixShop\Lakala\OpenAPISDK\V3\Model;
+
+class RelationRefundResponse extends ModelResponse implements \JsonSerializable
+{
+    // 商户号	M	String(32)	拉卡拉分配的商户号(请求接口中商户号)
+    protected $merchantNo;
+    // 商户请求流水号	M	String(32)	请求中的商户请求流水号
+    protected $outTradeNo;
+    // 拉卡拉退款单号	M	String(32)	拉卡拉交易流水号
+    protected $tradeNo;
+    // 拉卡拉对账单流水号	M	String(14)	拉卡拉对账单流水号
+    protected $logNo;
+    // 账户端交易订单号	C	String(48)	账户端交易流水号
+    protected $accTradeNo;
+    // 钱包类型	C	String(32)	微信:WECHAT 支付宝:ALIPAY 银联:UQRCODEPAY 翼支付: BESTPAY 苏宁易付宝: SUNING
+    protected $accountType;
+    // 交易金额	M	String(12)	单位分,整数数字型字符串
+    protected $totalAmount;
+    // 申请退款金额	M	String(12)	单位分,整数数字型字符串
+    protected $refundAmount;
+    // 	实际退款金额	M	String(12)	单位分,整数数字型字符串
+    protected $payerAmount;
+    // 退款时间	C	String(14)	实际退款时间。yyyyMMddHHmmss
+    protected $tradeTime;
+    // 原拉卡拉订单号	C	String(32)	如果请求中携带,则返回
+    protected $originTradeNo;
+    // 原商户请求流水号	C	String(64)	如果请求中携带,则返回
+    protected $originOutTradeNo;
+    // 单品营销 附加数据	C	String(8000)	参与单品营销优惠时返回
+    protected $upIssAddnData;
+    // 银联优惠信息、出资方信息	C	String(500)	参与单品营销优惠时返回
+    protected $upCouponInfo;
+    // 出资方信息	C	String(512)	数字货币中行返回示例说明:[{“fundchannel”:”BOC”,”amount”:”18”}]
+    protected $tradeInfo;
+    
+    public function setMerchantNo($merchantNo)
+    {
+        $this->merchantNo = $merchantNo;
+        return $this;
+    }
+    
+    public function getMerchantNo()
+    {
+        return $this->merchantNo;
+    }
+    
+    public function setOutTradeNo($outTradeNo)
+    {
+        $this->outTradeNo = $outTradeNo;
+        return $this;
+    }
+    
+    public function getOutTradeNo()
+    {
+        return $this->outTradeNo;
+    }
+    
+    public function setTradeNo($tradeNo)
+    {
+        $this->tradeNo = $tradeNo;
+        return $this;
+    }
+    
+    public function getTradeNo()
+    {
+        return $this->tradeNo;
+    }
+    
+    public function setLogNo($logNo)
+    {
+        $this->logNo = $logNo;
+        return $this;
+    }
+    
+    public function getLogNo()
+    {
+        return $this->logNo;
+    }
+    
+    public function setAccTradeNo($accTradeNo)
+    {
+        $this->accTradeNo = $accTradeNo;
+        return $this;
+    }
+    
+    public function getAccTradeNo()
+    {
+        return $this->accTradeNo;
+    }
+    
+    public function setAccountType($accountType)
+    {
+        $this->accountType = $accountType;
+        return $this;
+    }
+    
+    public function getAccountType()
+    {
+        return $this->accountType;
+    }
+    
+    public function setTotalAmount($totalAmount)
+    {
+        $this->totalAmount = $totalAmount;
+        return $this;
+    }
+    
+    public function getTotalAmount()
+    {
+        return $this->totalAmount;
+    }
+    
+    public function setRefundAmount($refundAmount)
+    {
+        $this->refundAmount = $refundAmount;
+        return $this;
+    }
+    
+    public function getRefundAmount()
+    {
+        return $this->refundAmount;
+    }
+    
+    public function setPayerAmount($payerAmount)
+    {
+        $this->payerAmount = $payerAmount;
+        return $this;
+    }
+    
+    public function getPayerAmount()
+    {
+        return $this->payerAmount;
+    }
+    
+    public function setTradeTime($tradeTime)
+    {
+        $this->tradeTime = $tradeTime;
+        return $this;
+    }
+    
+    public function getTradeTime()
+    {
+        return $this->tradeTime;
+    }
+    
+    public function setOriginTradeNo($originTradeNo)
+    {
+        $this->originTradeNo = $originTradeNo;
+        return $this;
+    }
+    
+    public function getOriginTradeNo()
+    {
+        return $this->originTradeNo;
+    }
+    
+    public function setOriginOutTradeNo($originOutTradeNo)
+    {
+        $this->originOutTradeNo = $originOutTradeNo;
+        return $this;
+    }
+    
+    public function getOriginOutTradeNo()
+    {
+        return $this->originOutTradeNo;
+    }
+    
+    public function setUpIssAddnData($upIssAddnData)
+    {
+        $this->upIssAddnData = $upIssAddnData;
+        return $this;
+    }
+    
+    public function getUpIssAddnData()
+    {
+        return $this->upIssAddnData;
+    }
+    
+    public function setUpCouponInfo($upCouponInfo)
+    {
+        $this->upCouponInfo = $upCouponInfo;
+        return $this;
+    }
+    
+    public function getUpCouponInfo()
+    {
+        return $this->upCouponInfo;
+    }
+    
+    public function setTradeInfo($tradeInfo)
+    {
+        $this->tradeInfo = $tradeInfo;
+        return $this;
+    }
+    
+    public function getTradeInfo()
+    {
+        return $this->tradeInfo;
+    }
+}

+ 24 - 0
src/OpenAPISDK/V3/Model/TradeAccBusiFields.php

@@ -0,0 +1,24 @@
+<?php
+/**
+ * acc_busi_fields 字段
+ * TradeAccBusiFields
+ * PHP version 7.4
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK\V3\Model
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+
+namespace SixShop\Lakala\OpenAPISDK\V3\Model;
+
+class TradeAccBusiFields implements \JsonSerializable
+{
+
+    #[\ReturnTypeWillChange]
+    public function jsonSerialize(): mixed
+    {
+        return [
+        ];
+    }
+}

+ 23 - 0
src/OpenAPISDK/V3/Model/TradeExtendParams.php

@@ -0,0 +1,23 @@
+<?php
+/**
+ * extend_params 字段
+ * TradeExtendParams
+ * PHP version 7.4
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK\V3\Model
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+
+namespace SixShop\Lakala\OpenAPISDK\V3\Model;
+
+class TradeExtendParams implements \JsonSerializable
+{
+
+    public function jsonSerialize()
+    {
+        return [
+        ];
+    }
+}

+ 24 - 0
src/OpenAPISDK/V3/Model/TradeGoodsDetail.php

@@ -0,0 +1,24 @@
+<?php
+/**
+ * goods_detail 字段
+ * TradeGoodsDetail
+ * PHP version 7.4
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK\V3\Model
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+
+namespace SixShop\Lakala\OpenAPISDK\V3\Model;
+
+class TradeGoodsDetail implements \JsonSerializable
+{
+
+    #[\ReturnTypeWillChange]
+    public function jsonSerialize(): mixed
+    {
+        return [
+        ];
+    }
+}

+ 67 - 0
src/OpenAPISDK/V3/Model/TradeLocationInfo.php

@@ -0,0 +1,67 @@
+<?php
+/**
+ * 查询交易 请求字段
+ * TradeLocationInfo
+ * PHP version 7.4
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK\V3\Model
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+
+namespace SixShop\Lakala\OpenAPISDK\V3\Model;
+
+class TradeLocationInfo implements \JsonSerializable
+{
+    protected $requestIp;
+    protected $baseStation;
+    protected $location;
+
+    public function __construct($ip)
+    {
+        $this->requestIp = $ip;
+    }
+    
+    public function setRequestIp($requestIp)
+    {
+        $this->requestIp = $requestIp;
+        return $this;
+    }
+    
+    public function getRequestIp()
+    {
+        return $this->requestIp;
+    }
+    
+    public function setBaseStation($baseStation)
+    {
+        $this->baseStation = $baseStation;
+        return $this;
+    }
+    
+    public function getBaseStation()
+    {
+        return $this->baseStation;
+    }
+    
+    public function setLocation($location)
+    {
+        $this->location = $location;
+        return $this;
+    }
+    
+    public function getLocation()
+    {
+        return $this->location;
+    }
+
+    public function jsonSerialize()
+    {
+        return [
+            'request_ip' => $this->requestIp,
+            'base_station' => $this->baseStation,
+            'location' => $this->location,
+        ];
+    }
+}

+ 122 - 0
src/OpenAPISDK/V3/Model/TradeMicropayAlipayAccBusiFields.php

@@ -0,0 +1,122 @@
+<?php
+/**
+ * 支付宝被扫场景下acc_busi_fields域内容
+ * TradeMicropayAlipayAccBusiFields
+ * PHP version 7.4
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK\V3\Model
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+
+namespace SixShop\Lakala\OpenAPISDK\V3\Model;
+
+use SixShop\Lakala\OpenAPISDK\V3\ObjectSerializer;
+
+class TradeMicropayAlipayAccBusiFields extends TradeAccBusiFields implements \JsonSerializable
+{
+    protected $extendParams;
+    protected $businessParams;
+    protected $goodsDetail;
+    protected $storeId;
+    protected $timeoutExpress;
+    protected $disablePayChannels;
+    protected $minAge;
+    
+    public function setExtendParams($extendParams)
+    {
+        $this->extendParams = $extendParams;
+        return $this;
+    }
+    
+    public function getExtendParams()
+    {
+        return $this->extendParams;
+    }
+    
+    public function setBusinessParams($businessParams)
+    {
+        $this->businessParams = $businessParams;
+        return $this;
+    }
+    
+    public function getBusinessParams()
+    {
+        return $this->businessParams;
+    }
+    
+    public function setGoodsDetail($goodsDetail)
+    {
+        $this->goodsDetail = $goodsDetail;
+        return $this;
+    }
+    
+    public function getGoodsDetail()
+    {
+        return $this->goodsDetail;
+    }
+    
+    public function setStoreId($storeId)
+    {
+        $this->storeId = $storeId;
+        return $this;
+    }
+    
+    public function getStoreId()
+    {
+        return $this->storeId;
+    }
+    
+    public function setTimeoutExpress($timeoutExpress)
+    {
+        $this->timeoutExpress = $timeoutExpress;
+        return $this;
+    }
+    
+    public function getTimeoutExpress()
+    {
+        return $this->timeoutExpress;
+    }
+    
+    public function setDisablePayChannels($disablePayChannels)
+    {
+        $this->disablePayChannels = $disablePayChannels;
+        return $this;
+    }
+    
+    public function getDisablePayChannels()
+    {
+        return $this->disablePayChannels;
+    }
+    
+    public function setMinAge($minAge)
+    {
+        $this->minAge = $minAge;
+        return $this;
+    }
+    
+    public function getMinAge()
+    {
+        return $this->minAge;
+    }
+
+    public function jsonSerialize()
+    {
+        $goods = array();
+        if (isset($this->goodsDetail)) {
+            foreach ($this->goodsDetail as $value) {
+                $goods[] = $value->jsonSerialize();
+            }
+        }
+        return [
+            'extend_params' => ObjectSerializer::jsonencode($this->extendParams),
+            'business_params' => $this->businessParams,
+            'goods_detail' => ObjectSerializer::jsonencode($goods),
+            'store_id' => $this->storeId,
+            'timeout_express' => $this->timeoutExpress,
+            'disable_pay_channels' => $this->disablePayChannels,
+            'min_age' => $this->minAge,
+        ];
+    }
+}

+ 62 - 0
src/OpenAPISDK/V3/Model/TradeMicropayAlipayExtendParams.php

@@ -0,0 +1,62 @@
+<?php
+/**
+ * 支付宝被扫场景下 extend_params字段说明
+ * TradeMicropayAlipayExtendParams
+ * PHP version 7.4
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK\V3\Model
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+
+namespace SixShop\Lakala\OpenAPISDK\V3\Model;
+
+class TradeMicropayAlipayExtendParams extends TradeExtendParams implements \JsonSerializable
+{
+    protected $sysServiceProviderId;
+    protected $hbFqNum;
+    protected $hbFqSellerPercent;
+    
+    public function setSysServiceProviderId($sysServiceProviderId)
+    {
+        $this->sysServiceProviderId = $sysServiceProviderId;
+        return $this;
+    }
+    
+    public function getSysServiceProviderId()
+    {
+        return $this->sysServiceProviderId;
+    }
+    
+    public function setHbFqNum($hbFqNum)
+    {
+        $this->hbFqNum = $hbFqNum;
+        return $this;
+    }
+    
+    public function getHbFqNum()
+    {
+        return $this->hbFqNum;
+    }
+    
+    public function setHbFqSellerPercent($hbFqSellerPercent)
+    {
+        $this->hbFqSellerPercent = $hbFqSellerPercent;
+        return $this;
+    }
+    
+    public function getHbFqSellerPercent()
+    {
+        return $this->hbFqSellerPercent;
+    }
+
+    public function jsonSerialize()
+    {
+        return [
+            'sys_service_provider_id' => $this->sysServiceProviderId,
+            'hb_fq_num' => $this->hbFqNum,
+            'hb_fq_seller_percent' => $this->hbFqSellerPercent,
+        ];
+    }
+}

+ 140 - 0
src/OpenAPISDK/V3/Model/TradeMicropayAlipayGoodsDetail.php

@@ -0,0 +1,140 @@
+<?php
+/**
+ * goods_detail 支付宝goods_detail字段
+ * TradeMicropayAlipayGoodsDetail
+ * PHP version 7.4
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK\V3\Model
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+
+namespace SixShop\Lakala\OpenAPISDK\V3\Model;
+
+class TradeMicropayAlipayGoodsDetail extends TradeGoodsDetail implements \JsonSerializable
+{
+    protected $goodsId;
+    protected $alipayGoodsId;
+    protected $goodsName;
+    protected $quantity;
+    protected $price;
+    protected $goodsCategory;
+    protected $categoriesTree;
+    protected $body;
+    protected $showUrl;
+    
+    public function setGoodsId($goodsId)
+    {
+        $this->goodsId = $goodsId;
+        return $this;
+    }
+    
+    public function getGoodsId()
+    {
+        return $this->goodsId;
+    }
+    
+    public function setAlipayGoodsId($alipayGoodsId)
+    {
+        $this->alipayGoodsId = $alipayGoodsId;
+        return $this;
+    }
+    
+    public function getAlipayGoodsId()
+    {
+        return $this->alipayGoodsId;
+    }
+    
+    public function setGoodsName($goodsName)
+    {
+        $this->goodsName = $goodsName;
+        return $this;
+    }
+    
+    public function getGoodsName()
+    {
+        return $this->goodsName;
+    }
+    
+    public function setQuantity($quantity)
+    {
+        $this->quantity = $quantity;
+        return $this;
+    }
+    
+    public function getQuantity()
+    {
+        return $this->quantity;
+    }
+    
+    public function setPrice($price)
+    {
+        $this->price = $price;
+        return $this;
+    }
+    
+    public function getPrice()
+    {
+        return $this->price;
+    }
+    
+    public function setGoodsCategory($goodsCategory)
+    {
+        $this->goodsCategory = $goodsCategory;
+        return $this;
+    }
+    
+    public function getGoodsCategory()
+    {
+        return $this->goodsCategory;
+    }
+    
+    public function setCategoriesTree($categoriesTree)
+    {
+        $this->categoriesTree = $categoriesTree;
+        return $this;
+    }
+    
+    public function getCategoriesTree()
+    {
+        return $this->categoriesTree;
+    }
+    
+    public function setBody($body)
+    {
+        $this->body = $body;
+        return $this;
+    }
+    
+    public function getBody()
+    {
+        return $this->body;
+    }
+    
+    public function setShowUrl($showUrl)
+    {
+        $this->showUrl = $showUrl;
+        return $this;
+    }
+    
+    public function getShowUrl()
+    {
+        return $this->showUrl;
+    }
+    
+    public function jsonSerialize()
+    {
+        return [
+            'goods_id' => $this->goodsId,
+            'alipay_goods_id' => $this->alipayGoodsId,
+            'goods_name' => $this->goodsName,
+            'quantity' => $this->quantity,
+            'price' => $this->price,
+            'goods_category' => $this->goodsCategory,
+            'categories_tree' => $this->categoriesTree,
+            'body' => $this->body,
+            'show_url' => $this->showUrl,
+        ];
+    }
+}

+ 116 - 0
src/OpenAPISDK/V3/Model/TradeMicropayWechaAccBusiFields.php

@@ -0,0 +1,116 @@
+<?php
+/**
+ * 微信被扫场景下acc_busi_fields域内容
+ * TradeMicropayWechaAccBusiFields
+ * PHP version 7.4
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK\V3\Model
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+
+namespace SixShop\Lakala\OpenAPISDK\V3\Model;
+
+use SixShop\Lakala\OpenAPISDK\V3\ObjectSerializer;
+
+class TradeMicropayWechaAccBusiFields extends TradeAccBusiFields implements \JsonSerializable
+{
+    protected $subAppid;
+    protected $detail;
+    protected $goodsTag;
+    protected $deviceInfo;
+    protected $limitPay;
+    protected $sceneInfo;
+    protected $limitPayer;
+    
+    public function setSubAppid($subAppid)
+    {
+        $this->subAppid = $subAppid;
+        return $this;
+    }
+    
+    public function getSubAppid()
+    {
+        return $this->subAppid;
+    }
+    
+    public function setDetail($detail)
+    {
+        $this->detail = $detail;
+        return $this;
+    }
+    
+    public function getDetail()
+    {
+        return $this->detail;
+    }
+    
+    public function setGoodsTag($goodsTag)
+    {
+        $this->goodsTag = $goodsTag;
+        return $this;
+    }
+    
+    public function getGoodsTag()
+    {
+        return $this->goodsTag;
+    }
+    
+    public function setDeviceInfo($deviceInfo)
+    {
+        $this->deviceInfo = $deviceInfo;
+        return $this;
+    }
+    
+    public function getDeviceInfo()
+    {
+        return $this->deviceInfo;
+    }
+    
+    public function setLimitPay($limitPay)
+    {
+        $this->limitPay = $limitPay;
+        return $this;
+    }
+    
+    public function getLimitPay()
+    {
+        return $this->limitPay;
+    }
+    
+    public function setSceneInfo($sceneInfo)
+    {
+        $this->sceneInfo = $sceneInfo;
+        return $this;
+    }
+    
+    public function getSceneInfo()
+    {
+        return $this->sceneInfo;
+    }
+    
+    public function setLimitPayer($limitPayer)
+    {
+        $this->limitPayer = $limitPayer;
+        return $this;
+    }
+    
+    public function getLimitPayer()
+    {
+        return $this->limitPayer;
+    }
+
+    public function jsonSerialize()
+    {
+        return [
+            'sub_appid' => $this->subAppid,
+            'detail' => ObjectSerializer::jsonencode($this->detail),
+            'goods_tag' => $this->goodsTag,
+            'device_info' => $this->deviceInfo,
+            'limit_pay' => $this->limitPay,
+            'scene_info' => $this->sceneInfo,
+            'limit_payer' => $this->limitPayer,
+        ];
+    }
+}

+ 70 - 0
src/OpenAPISDK/V3/Model/TradeMicropayWechaDetail.php

@@ -0,0 +1,70 @@
+<?php
+/**
+ * 支付宝被扫场景下 extend_params字段说明
+ * TradeMicropayWechaDetail
+ * PHP version 7.4
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK\V3\Model
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+
+namespace SixShop\Lakala\OpenAPISDK\V3\Model;
+
+use SixShop\Lakala\OpenAPISDK\V3\ObjectSerializer;
+
+class TradeMicropayWechaDetail implements \JsonSerializable
+{
+    protected $costPrice;
+    protected $receiptId;
+    protected $goodsDetail;
+    
+    public function setCostPrice($costPrice)
+    {
+        $this->costPrice = $costPrice;
+        return $this;
+    }
+
+    public function getCostPrice()
+    {
+        return $this->costPrice;
+    }
+
+    public function setReceiptId($receiptId)
+    {
+        $this->receiptId = $receiptId;
+        return $this;
+    }
+
+    public function getReceiptId()
+    {
+        return $this->receiptId;
+    }
+    
+    public function setGoodsDetail($goodsDetail)
+    {
+        $this->goodsDetail = $goodsDetail;
+        return $this;
+    }
+    
+    public function getGoodsDetail()
+    {
+        return $this->goodsDetail;
+    }
+
+    public function jsonSerialize()
+    {
+        $goods = array();
+        if (isset($this->goodsDetail)) {
+            foreach ($this->goodsDetail as $value) {
+                $goods[] = $value->jsonSerialize();
+            }
+        }
+        return [
+            'cost_price' => $this->costPrice,
+            'receipt_id' => $this->receiptId,
+            'goods_detail' => $goods,
+        ];
+    }
+}

+ 88 - 0
src/OpenAPISDK/V3/Model/TradeMicropayWechaGoodsDetail.php

@@ -0,0 +1,88 @@
+<?php
+/**
+ * 微信goods_detail字段说明
+ * TradeMicropayWechaGoodsDetail
+ * PHP version 7.4
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK\V3\Model
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+
+namespace SixShop\Lakala\OpenAPISDK\V3\Model;
+
+class TradeMicropayWechaGoodsDetail extends TradeGoodsDetail implements \JsonSerializable
+{
+    protected $goodsId;
+    protected $wxpayGoodsId;
+    protected $goodsName;
+    protected $quantity;
+    protected $price;
+    
+    public function setGoodsId($goodsId)
+    {
+        $this->goodsId = $goodsId;
+        return $this;
+    }
+    
+    public function getGoodsId()
+    {
+        return $this->goodsId;
+    }
+    
+    public function setWxpayGoodsId($wxpayGoodsId)
+    {
+        $this->wxpayGoodsId = $wxpayGoodsId;
+        return $this;
+    }
+    
+    public function getWxpayGoodsId()
+    {
+        return $this->wxpayGoodsId;
+    }
+    
+    public function setGoodsName($goodsName)
+    {
+        $this->goodsName = $goodsName;
+        return $this;
+    }
+    
+    public function getGoodsName()
+    {
+        return $this->goodsName;
+    }
+    
+    public function setQuantity($quantity)
+    {
+        $this->quantity = $quantity;
+        return $this;
+    }
+    
+    public function getQuantity()
+    {
+        return $this->quantity;
+    }
+    
+    public function setPrice($price)
+    {
+        $this->price = $price;
+        return $this;
+    }
+    
+    public function getPrice()
+    {
+        return $this->price;
+    }
+
+    public function jsonSerialize()
+    {
+        return [
+            'goods_id' => $this->goodsId,
+            'wxpay_goods_id' => $this->wxpayGoodsId,
+            'goods_name' => $this->goodsName,
+            'quantity' => $this->quantity,
+            'price' => $this->price,
+        ];
+    }
+}

+ 148 - 0
src/OpenAPISDK/V3/Model/TradePreorderAlipayAccBusiFields.php

@@ -0,0 +1,148 @@
+<?php
+/**
+ * 支付宝主扫场景下acc_busi_fields域内容
+ * TradePreorderAlipayAccBusiFields
+ * PHP version 7.4
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK\V3\Model
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+
+namespace SixShop\Lakala\OpenAPISDK\V3\Model;
+
+use SixShop\Lakala\OpenAPISDK\V3\ObjectSerializer;
+
+class TradePreorderAlipayAccBusiFields extends TradeAccBusiFields implements \JsonSerializable
+{
+    protected $userId;
+    protected $timeoutExpress;
+    protected $extendParams;
+    protected $goodsDetail;
+    protected $quitUrl;
+    protected $storeId;
+    protected $disablePayChannels;
+    protected $businessParams;
+    protected $minAge;
+    
+    public function setUserId($userId)
+    {
+        $this->userId = $userId;
+        return $this;
+    }
+    
+    public function getUserId()
+    {
+        return $this->userId;
+    }
+    
+    public function setTimeoutExpress($timeoutExpress)
+    {
+        $this->timeoutExpress = $timeoutExpress;
+        return $this;
+    }
+    
+    public function getTimeoutExpress()
+    {
+        return $this->timeoutExpress;
+    }
+    
+    public function setExtendParams($extendParams)
+    {
+        $this->extendParams = $extendParams;
+        return $this;
+    }
+    
+    public function getExtendParams()
+    {
+        return $this->extendParams;
+    }
+    
+    public function setGoodsDetail($goodsDetail)
+    {
+        $this->goodsDetail = $goodsDetail;
+        return $this;
+    }
+    
+    public function getGoodsDetail()
+    {
+        return $this->goodsDetail;
+    }
+    
+    public function setQuitUrl($quitUrl)
+    {
+        $this->quitUrl = $quitUrl;
+        return $this;
+    }
+    
+    public function getQuitUrl()
+    {
+        return $this->quitUrl;
+    }
+    
+    public function setStoreId($storeId)
+    {
+        $this->storeId = $storeId;
+        return $this;
+    }
+    
+    public function getStoreId()
+    {
+        return $this->storeId;
+    }
+    
+    public function setDisablePayChannels($disablePayChannels)
+    {
+        $this->disablePayChannels = $disablePayChannels;
+        return $this;
+    }
+    
+    public function getDisablePayChannels()
+    {
+        return $this->disablePayChannels;
+    }
+    
+    public function setBusinessParams($businessParams)
+    {
+        $this->businessParams = $businessParams;
+        return $this;
+    }
+    
+    public function getBusinessParams()
+    {
+        return $this->businessParams;
+    }
+    
+    public function setMinAge($minAge)
+    {
+        $this->minAge = $minAge;
+        return $this;
+    }
+    
+    public function getMinAge()
+    {
+        return $this->minAge;
+    }
+
+    public function jsonSerialize()
+    {
+        $goods = array();
+        if (isset($this->goodsDetail)) {
+            foreach ($this->goodsDetail as $value) {
+                $goods[] = $value->jsonSerialize();
+            }
+        }
+        return [
+            'user_id' => $this->userId,
+            'timeout_express' => $this->timeoutExpress,
+            'extend_params' => ObjectSerializer::jsonencode($this->extendParams),
+            'goods_detail' => ObjectSerializer::jsonencode($goods),
+            'quit_url' => $this->quitUrl,
+            'store_id' => $this->storeId,
+            'disable_pay_channels' => $this->disablePayChannels,
+            'business_params' => $this->businessParams,
+            'min_age' => $this->minAge
+        ];
+    }
+}

+ 75 - 0
src/OpenAPISDK/V3/Model/TradePreorderAlipayExtendParams.php

@@ -0,0 +1,75 @@
+<?php
+/**
+ * 支付宝主扫场景下 extend_params字段说明
+ * TradePreorderAlipayExtendParams
+ * PHP version 7.4
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK\V3\Model
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+
+namespace SixShop\Lakala\OpenAPISDK\V3\Model;
+
+class TradePreorderAlipayExtendParams extends TradeExtendParams implements \JsonSerializable
+{
+    protected $sysServiceProviderId;
+    protected $hbFqNum;
+    protected $hbFqSellerPercent;
+    protected $foodOrderType;
+    
+    public function setSysServiceProviderId($sysServiceProviderId)
+    {
+        $this->sysServiceProviderId = $sysServiceProviderId;
+        return $this;
+    }
+    
+    public function getSysServiceProviderId()
+    {
+        return $this->sysServiceProviderId;
+    }
+    
+    public function setHbFqNum($hbFqNum)
+    {
+        $this->hbFqNum = $hbFqNum;
+        return $this;
+    }
+    
+    public function getHbFqNum()
+    {
+        return $this->hbFqNum;
+    }
+    
+    public function setHbFqSellerPercent($hbFqSellerPercent)
+    {
+        $this->hbFqSellerPercent = $hbFqSellerPercent;
+        return $this;
+    }
+    
+    public function getHbFqSellerPercent()
+    {
+        return $this->hbFqSellerPercent;
+    }
+    
+    public function setFoodOrderType($foodOrderType)
+    {
+        $this->foodOrderType = $foodOrderType;
+        return $this;
+    }
+    
+    public function getFoodOrderType()
+    {
+        return $this->foodOrderType;
+    }
+
+    public function jsonSerialize()
+    {
+        return [
+            'sys_service_provider_id' => $this->sysServiceProviderId,
+            'hb_fq_num' => $this->hbFqNum,
+            'hb_fq_seller_percent' => $this->hbFqSellerPercent,
+            'food_order_type' => $this->foodOrderType,
+        ];
+    }
+}

+ 140 - 0
src/OpenAPISDK/V3/Model/TradePreorderAlipayGoodsDetail.php

@@ -0,0 +1,140 @@
+<?php
+/**
+ * goods_detail 支付宝goods_detail字段
+ * TradePreorderAlipayGoodsDetail
+ * PHP version 7.4
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK\V3\Model
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+
+namespace SixShop\Lakala\OpenAPISDK\V3\Model;
+
+class TradePreorderAlipayGoodsDetail extends TradeGoodsDetail implements \JsonSerializable
+{
+    protected $goodsId;
+    protected $alipayGoodsId;
+    protected $goodsName;
+    protected $quantity;
+    protected $price;
+    protected $goodsCategory;
+    protected $categoriesTree;
+    protected $body;
+    protected $showUrl;
+    
+    public function setGoodsId($goodsId)
+    {
+        $this->goodsId = $goodsId;
+        return $this;
+    }
+    
+    public function getGoodsId()
+    {
+        return $this->goodsId;
+    }
+    
+    public function setAlipayGoodsId($alipayGoodsId)
+    {
+        $this->alipayGoodsId = $alipayGoodsId;
+        return $this;
+    }
+    
+    public function getAlipayGoodsId()
+    {
+        return $this->alipayGoodsId;
+    }
+    
+    public function setGoodsName($goodsName)
+    {
+        $this->goodsName = $goodsName;
+        return $this;
+    }
+    
+    public function getGoodsName()
+    {
+        return $this->goodsName;
+    }
+    
+    public function setQuantity($quantity)
+    {
+        $this->quantity = $quantity;
+        return $this;
+    }
+    
+    public function getQuantity()
+    {
+        return $this->quantity;
+    }
+    
+    public function setPrice($price)
+    {
+        $this->price = $price;
+        return $this;
+    }
+    
+    public function getPrice()
+    {
+        return $this->price;
+    }
+    
+    public function setGoodsCategory($goodsCategory)
+    {
+        $this->goodsCategory = $goodsCategory;
+        return $this;
+    }
+    
+    public function getGoodsCategory()
+    {
+        return $this->goodsCategory;
+    }
+    
+    public function setCategoriesTree($categoriesTree)
+    {
+        $this->categoriesTree = $categoriesTree;
+        return $this;
+    }
+    
+    public function getCategoriesTree()
+    {
+        return $this->categoriesTree;
+    }
+    
+    public function setBody($body)
+    {
+        $this->body = $body;
+        return $this;
+    }
+    
+    public function getBody()
+    {
+        return $this->body;
+    }
+    
+    public function setShowUrl($showUrl)
+    {
+        $this->showUrl = $showUrl;
+        return $this;
+    }
+    
+    public function getShowUrl()
+    {
+        return $this->showUrl;
+    }
+
+    public function jsonSerialize()
+    {
+        return [
+            'goods_id' => $this->goodsId,
+            'alipay_goods_id' => $this->alipayGoodsId,
+            'goods_name' => $this->goodsName,
+            'quantity' => $this->quantity,
+            'price' => $this->price,
+            'goods_category' => $this->goodsCategory,
+            'categories_tree' => $this->categoriesTree,
+            'body' => $this->body,
+            'show_url' => $this->showUrl,
+        ];
+    }
+}

+ 36 - 0
src/OpenAPISDK/V3/Model/TradePreorderNucspayAccBusiFields.php

@@ -0,0 +1,36 @@
+<?php
+/**
+ * 网联小钱包acc_busi_fields域内容
+ * TradePreorderNucspayAccBusiFields
+ * PHP version 7.4
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK\V3\Model
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+
+namespace SixShop\Lakala\OpenAPISDK\V3\Model;
+
+class TradePreorderNucspayAccBusiFields extends TradeAccBusiFields implements \JsonSerializable
+{
+    protected $nucIssrId;
+
+    public function setNucIssrId($nucIssrId)
+    {
+        $this->nucIssrId = $nucIssrId;
+        return $this;
+    }
+    
+    public function getNucIssrId()
+    {
+        return $this->nucIssrId;
+    }
+
+    public function jsonSerialize()
+    {
+        return [
+            'nuc_issr_id' => $this->nucIssrId,
+        ];
+    }
+}

+ 148 - 0
src/OpenAPISDK/V3/Model/TradePreorderUnionPayAccBusiFields.php

@@ -0,0 +1,148 @@
+<?php
+/**
+ * 银联云闪付主扫场景下acc_busi_fields域内容
+ * TradePreorderUnionPayAccBusiFields
+ * PHP version 7.4
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK\V3\Model
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+
+namespace SixShop\Lakala\OpenAPISDK\V3\Model;
+
+use SixShop\Lakala\OpenAPISDK\V3\ObjectSerializer;
+
+class TradePreorderUnionPayAccBusiFields extends TradeAccBusiFields implements \JsonSerializable
+{
+    protected $userId;
+    protected $timeoutExpress;
+    protected $acqAddnDataOrderInfo;
+    protected $acqAddnDataGoodsInfo;
+    protected $frontUrl;
+    protected $frontFailUrl;
+    protected $instalWill;
+    protected $unQrcode;
+    protected $userAuthCode;
+    
+    public function setUserId($userId)
+    {
+        $this->userId = $userId;
+        return $this;
+    }
+    
+    public function getUserId()
+    {
+        return $this->userId;
+    }
+    
+    public function setTimeoutExpress($timeoutExpress)
+    {
+        $this->timeoutExpress = $timeoutExpress;
+        return $this;
+    }
+    
+    public function getTimeoutExpress()
+    {
+        return $this->timeoutExpress;
+    }
+    
+    public function setAcqAddnDataOrderInfo($acqAddnDataOrderInfo)
+    {
+        $this->acqAddnDataOrderInfo = $acqAddnDataOrderInfo;
+        return $this;
+    }
+    
+    public function getAcqAddnDataOrderInfo()
+    {
+        return $this->acqAddnDataOrderInfo;
+    }
+    
+    public function setAcqAddnDataGoodsInfo($acqAddnDataGoodsInfo)
+    {
+        $this->acqAddnDataGoodsInfo = $acqAddnDataGoodsInfo;
+        return $this;
+    }
+    
+    public function getAcqAddnDataGoodsInfo()
+    {
+        return $this->acqAddnDataGoodsInfo;
+    }
+    
+    public function setFrontUrl($frontUrl)
+    {
+        $this->frontUrl = $frontUrl;
+        return $this;
+    }
+    
+    public function getFrontUrl()
+    {
+        return $this->frontUrl;
+    }
+    
+    public function setFrontFailUrl($frontFailUrl)
+    {
+        $this->frontFailUrl = $frontFailUrl;
+        return $this;
+    }
+    
+    public function getFrontFailUrl()
+    {
+        return $this->frontFailUrl;
+    }
+    
+    public function setInstalWill($instalWill)
+    {
+        $this->instalWill = $instalWill;
+        return $this;
+    }
+    
+    public function getInstalWill()
+    {
+        return $this->instalWill;
+    }
+    
+    public function setUnQrcode($unQrcode)
+    {
+        $this->unQrcode = $unQrcode;
+        return $this;
+    }
+    
+    public function getUnQrcode()
+    {
+        return $this->unQrcode;
+    }
+    
+    public function setUserAuthCode($userAuthCode)
+    {
+        $this->userAuthCode = $userAuthCode;
+        return $this;
+    }
+    
+    public function getUserAuthCode()
+    {
+        return $this->userAuthCode;
+    }
+
+    public function jsonSerialize()
+    {
+        $goods = array();
+        if (isset($this->acqAddnDataGoodsInfo)) {
+            foreach ($this->acqAddnDataGoodsInfo as $value) {
+                $goods[] = $value->jsonSerialize();
+            }
+        }
+        return [
+            'user_id' => $this->userId,
+            'timeout_express' => $this->timeoutExpress,
+            'acq_addn_data_order_info' => ObjectSerializer::jsonencode($this->acqAddnDataOrderInfo),
+            'acq_addn_data_goods_info' => $goods,
+            'front_url' => $this->frontUrl,
+            'front_fail_url' => $this->frontFailUrl,
+            'instal_will' => $this->instalWill,
+            'un_qrcode' => $this->unQrcode,
+            'user_auth_code' => $this->userAuthCode,
+        ];
+    }
+}

+ 101 - 0
src/OpenAPISDK/V3/Model/TradePreorderUnionPayAcqAddnDataGoodsInfo.php

@@ -0,0 +1,101 @@
+<?php
+/**
+ * 银联云闪acq_addn_data_order_info字段
+ * TradePreorderUnionPayAcqAddnDataGoodsInfo
+ * PHP version 7.4
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK\V3\Model
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+
+namespace SixShop\Lakala\OpenAPISDK\V3\Model;
+
+class TradePreorderUnionPayAcqAddnDataGoodsInfo implements \JsonSerializable
+{
+    protected $id;
+    protected $name;
+    protected $price;
+    protected $quantity;
+    protected $category;
+    protected $addninfo;
+    
+    public function setId($id)
+    {
+        $this->id = $id;
+        return $this;
+    }
+    
+    public function getId()
+    {
+        return $this->id;
+    }
+    
+    public function setName($name)
+    {
+        $this->name = $name;
+        return $this;
+    }
+    
+    public function getName()
+    {
+        return $this->name;
+    }
+    
+    public function setPrice($price)
+    {
+        $this->price = $price;
+        return $this;
+    }
+    
+    public function getPrice()
+    {
+        return $this->price;
+    }
+    
+    public function setQuantity($quantity)
+    {
+        $this->quantity = $quantity;
+        return $this;
+    }
+    
+    public function getQuantity()
+    {
+        return $this->quantity;
+    }
+    
+    public function setCategory($category)
+    {
+        $this->category = $category;
+        return $this;
+    }
+    
+    public function getCategory()
+    {
+        return $this->category;
+    }
+    
+    public function setAddninfo($addninfo)
+    {
+        $this->addninfo = $addninfo;
+        return $this;
+    }
+    
+    public function getAddninfo()
+    {
+        return $this->addninfo;
+    }
+
+    public function jsonSerialize()
+    {
+        return [
+            'id' => $this->id,
+            'name' => $this->name,
+            'price' => $this->price,
+            'quantity' => $this->quantity,
+            'category' => $this->category,
+            'addninfo' => $this->addninfo,
+        ];
+    }
+}

+ 62 - 0
src/OpenAPISDK/V3/Model/TradePreorderUnionPayAcqAddnDataOrderInfo.php

@@ -0,0 +1,62 @@
+<?php
+/**
+ * 银联云闪acq_addn_data_order_info字段
+ * TradePreorderUnionPayAcqAddnDataOrderInfo
+ * PHP version 7.4
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK\V3\Model
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+
+namespace SixShop\Lakala\OpenAPISDK\V3\Model;
+
+class TradePreorderUnionPayAcqAddnDataOrderInfo implements \JsonSerializable
+{
+    protected $title;
+    protected $dctAmount;
+    protected $addnInfo;
+    
+    public function setTitle($title)
+    {
+        $this->title = $title;
+        return $this;
+    }
+    
+    public function getTitle()
+    {
+        return $this->title;
+    }
+    
+    public function setDctAmount($dctAmount)
+    {
+        $this->dctAmount = $dctAmount;
+        return $this;
+    }
+    
+    public function getDctAmount()
+    {
+        return $this->dctAmount;
+    }
+    
+    public function setAddnInfo($addnInfo)
+    {
+        $this->addnInfo = $addnInfo;
+        return $this;
+    }
+    
+    public function getAddnInfo()
+    {
+        return $this->addnInfo;
+    }
+
+    public function jsonSerialize()
+    {
+        return [
+            'title' => $this->title,
+            'dctAmount' => $this->dctAmount,
+            'addnInfo' => $this->addnInfo,
+        ];
+    }
+}

+ 62 - 0
src/OpenAPISDK/V3/Model/TradePreorderUnionPayAddnInfo.php

@@ -0,0 +1,62 @@
+<?php
+/**
+ * 银联云闪addn_info字段
+ * TradePreorderUnionPayAddnInfo
+ * PHP version 7.4
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK\V3\Model
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+
+namespace SixShop\Lakala\OpenAPISDK\V3\Model;
+
+class TradePreorderUnionPayAddnInfo implements \JsonSerializable
+{
+    protected $preproduct;
+    protected $lockplan;
+    protected $riskInfo;
+    
+    public function setPreproduct($preproduct)
+    {
+        $this->preproduct = $preproduct;
+        return $this;
+    }
+    
+    public function getPreproduct()
+    {
+        return $this->preproduct;
+    }
+    
+    public function setLockplan($lockplan)
+    {
+        $this->lockplan = $lockplan;
+        return $this;
+    }
+    
+    public function getLockplan()
+    {
+        return $this->lockplan;
+    }
+    
+    public function setRiskInfo($riskInfo)
+    {
+        $this->riskInfo = $riskInfo;
+        return $this;
+    }
+    
+    public function getRiskInfo()
+    {
+        return $this->riskInfo;
+    }
+
+    public function jsonSerialize()
+    {
+        return [
+            'preproduct' => $this->preproduct,
+            'lockplan' => $this->lockplan,
+            'riskInfo' => $this->riskInfo,
+        ];
+    }
+}

+ 88 - 0
src/OpenAPISDK/V3/Model/TradePreorderUnionPayRiskInfo.php

@@ -0,0 +1,88 @@
+<?php
+/**
+ * 银联云闪risk_info
+ * TradePreorderUnionPayRiskInfo
+ * PHP version 7.4
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK\V3\Model
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+
+namespace SixShop\Lakala\OpenAPISDK\V3\Model;
+
+class TradePreorderUnionPayRiskInfo implements \JsonSerializable
+{
+    protected $itemNo;
+    protected $orderSource;
+    protected $payUserId;
+    protected $payCodeId;
+    protected $merchantType;
+    
+    public function setItemNo($itemNo)
+    {
+        $this->itemNo = $itemNo;
+        return $this;
+    }
+    
+    public function getItemNo()
+    {
+        return $this->itemNo;
+    }
+    
+    public function setOrderSource($orderSource)
+    {
+        $this->orderSource = $orderSource;
+        return $this;
+    }
+    
+    public function getOrderSource()
+    {
+        return $this->orderSource;
+    }
+    
+    public function setPayUserId($payUserId)
+    {
+        $this->payUserId = $payUserId;
+        return $this;
+    }
+    
+    public function getPayUserId()
+    {
+        return $this->payUserId;
+    }
+    
+    public function setPayCodeId($payCodeId)
+    {
+        $this->payCodeId = $payCodeId;
+        return $this;
+    }
+    
+    public function getPayCodeId()
+    {
+        return $this->payCodeId;
+    }
+    
+    public function setMerchantType($merchantType)
+    {
+        $this->merchantType = $merchantType;
+        return $this;
+    }
+    
+    public function getMerchantType()
+    {
+        return $this->merchantType;
+    }    
+
+    public function jsonSerialize()
+    {
+        return [
+            'itemNo' => $this->itemNo,
+            'orderSource' => $this->orderSource,
+            'payUserId' => $this->payUserId,
+            'payCodeId' => $this->payCodeId,
+            'merchantType' => $this->merchantType,
+        ];
+    }
+}

+ 156 - 0
src/OpenAPISDK/V3/Model/TradePreorderWechaAccBusiFields.php

@@ -0,0 +1,156 @@
+<?php
+/**
+ * 微信主扫场景下acc_busi_fields域内容
+ * TradePreorderWechaAccBusiFields
+ * PHP version 7.4
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK\V3\Model
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+
+namespace SixShop\Lakala\OpenAPISDK\V3\Model;
+
+use SixShop\Lakala\OpenAPISDK\V3\ObjectSerializer;
+
+class TradePreorderWechaAccBusiFields extends TradeAccBusiFields implements \JsonSerializable
+{
+    protected $timeoutExpress;
+    protected $subAppid;
+    protected $userId;
+    protected $detail;
+    protected $goodsTag;
+    protected $attach;
+    protected $deviceInfo;
+    protected $limitPay;
+    protected $sceneInfo;
+    protected $limitPayer;
+    
+    public function setTimeoutExpress($timeoutExpress)
+    {
+        $this->timeoutExpress = $timeoutExpress;
+        return $this;
+    }
+    
+    public function getTimeoutExpress()
+    {
+        return $this->timeoutExpress;
+    }
+    
+    public function setSubAppid($subAppid)
+    {
+        $this->subAppid = $subAppid;
+        return $this;
+    }
+    
+    public function getSubAppid()
+    {
+        return $this->subAppid;
+    }
+    
+    public function setUserId($userId)
+    {
+        $this->userId = $userId;
+        return $this;
+    }
+    
+    public function getUserId()
+    {
+        return $this->userId;
+    }
+    
+    public function setDetail($detail)
+    {
+        $this->detail = $detail;
+        return $this;
+    }
+    
+    public function getDetail()
+    {
+        return $this->detail;
+    }
+    
+    public function setGoodsTag($goodsTag)
+    {
+        $this->goodsTag = $goodsTag;
+        return $this;
+    }
+    
+    public function getGoodsTag()
+    {
+        return $this->goodsTag;
+    }
+    
+    public function setAttach($attach)
+    {
+        $this->attach = $attach;
+        return $this;
+    }
+    
+    public function getAttach()
+    {
+        return $this->attach;
+    }
+    
+    public function setDeviceInfo($deviceInfo)
+    {
+        $this->deviceInfo = $deviceInfo;
+        return $this;
+    }
+    
+    public function getDeviceInfo()
+    {
+        return $this->deviceInfo;
+    }
+    
+    public function setLimitPay($limitPay)
+    {
+        $this->limitPay = $limitPay;
+        return $this;
+    }
+    
+    public function getLimitPay()
+    {
+        return $this->limitPay;
+    }
+    
+    public function setSceneInfo($sceneInfo)
+    {
+        $this->sceneInfo = $sceneInfo;
+        return $this;
+    }
+    
+    public function getSceneInfo()
+    {
+        return $this->sceneInfo;
+    }
+    
+    public function setLimitPayer($limitPayer)
+    {
+        $this->limitPayer = $limitPayer;
+        return $this;
+    }
+    
+    public function getLimitPayer()
+    {
+        return $this->limitPayer;
+    }
+
+    #[\ReturnTypeWillChange]
+    public function jsonSerialize(): mixed
+    {
+        return [
+            'timeout_express' => $this->timeoutExpress,
+            'sub_appid' => $this->subAppid,
+            'user_id' => $this->userId,
+            'detail' => ObjectSerializer::jsonencode($this->detail),
+            'goods_tag' => $this->goodsTag,
+            'attach' => $this->attach,
+            'device_info' => $this->deviceInfo,
+            'limit_pay' => $this->limitPay,
+            'scene_info' => $this->sceneInfo,
+            'limit_payer' => $this->limitPayer,
+        ];
+    }
+}

+ 71 - 0
src/OpenAPISDK/V3/Model/TradePreorderWechaDetail.php

@@ -0,0 +1,71 @@
+<?php
+/**
+ * 支付宝主扫场景下 extend_params字段说明
+ * TradePreorderWechaExtendParams
+ * PHP version 7.4
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK\V3\Model
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+
+namespace SixShop\Lakala\OpenAPISDK\V3\Model;
+
+use SixShop\Lakala\OpenAPISDK\V3\ObjectSerializer;
+
+class TradePreorderWechaDetail implements \JsonSerializable
+{
+    protected $costPrice;
+    protected $receiptId;
+    protected $goodsDetail;
+    
+    public function setCostPrice($costPrice)
+    {
+        $this->costPrice = $costPrice;
+        return $this;
+    }
+
+    public function getCostPrice()
+    {
+        return $this->costPrice;
+    }
+
+    public function setReceiptId($receiptId)
+    {
+        $this->receiptId = $receiptId;
+        return $this;
+    }
+
+    public function getReceiptId()
+    {
+        return $this->receiptId;
+    }
+    
+    public function setGoodsDetail($goodsDetail)
+    {
+        $this->goodsDetail = $goodsDetail;
+        return $this;
+    }
+    
+    public function getGoodsDetail()
+    {
+        return $this->goodsDetail;
+    }
+
+    #[\ReturnTypeWillChange]
+    public function jsonSerialize(): mixed
+    {
+        $goods = array();
+        if (isset($this->goodsDetail)) {
+            foreach ($this->goodsDetail as $value) {
+                $goods[] = $value->jsonSerialize();
+            }
+        }
+        return [
+            'cost_price' => $this->costPrice,
+            'receipt_id' => $this->receiptId,
+            'goods_detail' => $goods,
+        ];
+    }
+}

+ 89 - 0
src/OpenAPISDK/V3/Model/TradePreorderWechaGoodsDetail.php

@@ -0,0 +1,89 @@
+<?php
+/**
+ * 微信goods_detail字段说明
+ * TradePreorderWechaGoodsDetail
+ * PHP version 7.4
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK\V3\Model
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+
+namespace SixShop\Lakala\OpenAPISDK\V3\Model;
+
+class TradePreorderWechaGoodsDetail extends TradeGoodsDetail implements \JsonSerializable
+{
+    protected $goodsId;
+    protected $wxpayGoodsId;
+    protected $goodsName;
+    protected $quantity;
+    protected $price;
+    
+    public function setGoodsId($goodsId)
+    {
+        $this->goodsId = $goodsId;
+        return $this;
+    }
+    
+    public function getGoodsId()
+    {
+        return $this->goodsId;
+    }
+    
+    public function setWxpayGoodsId($wxpayGoodsId)
+    {
+        $this->wxpayGoodsId = $wxpayGoodsId;
+        return $this;
+    }
+    
+    public function getWxpayGoodsId()
+    {
+        return $this->wxpayGoodsId;
+    }
+    
+    public function setGoodsName($goodsName)
+    {
+        $this->goodsName = $goodsName;
+        return $this;
+    }
+    
+    public function getGoodsName()
+    {
+        return $this->goodsName;
+    }
+    
+    public function setQuantity($quantity)
+    {
+        $this->quantity = $quantity;
+        return $this;
+    }
+    
+    public function getQuantity()
+    {
+        return $this->quantity;
+    }
+    
+    public function setPrice($price)
+    {
+        $this->price = $price;
+        return $this;
+    }
+    
+    public function getPrice()
+    {
+        return $this->price;
+    }
+
+    #[\ReturnTypeWillChange]
+    public function jsonSerialize(): mixed
+    {
+        return [
+            'goods_id' => $this->goodsId,
+            'wxpay_goods_id' => $this->wxpayGoodsId,
+            'goods_name' => $this->goodsName,
+            'quantity' => $this->quantity,
+            'price' => $this->price,
+        ];
+    }
+}

+ 306 - 0
src/OpenAPISDK/V3/Model/TransMicropayRequest.php

@@ -0,0 +1,306 @@
+<?php
+/**
+ * 被扫交易 请求字段
+ * TransMicropayRequest
+ * PHP version 7.4
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK\V3\Model
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+
+namespace SixShop\Lakala\OpenAPISDK\V3\Model;
+
+class TransMicropayRequest extends ModelRequest implements \JsonSerializable
+{
+    // 商户号	M	String(32)	拉卡拉分配的商户号
+    protected $merchantNo;
+    // 终端号	M	String(32)	拉卡拉分配的业务终端号
+    protected $termNo;
+    // 商户交易流水号	M	String(32)	商户系统唯一,不可重复
+    protected $outTradeNo;
+    /**
+     * 支付授权码	M	String(32)	扫码支付授权码,设备读取用户APP中的条码或者二维码信息,用户付款码条形码规则见说明
+     * 
+     * 属性	    说明	备注
+     * 微信	    WECHAT	付款码10 11 12 13 14 15开头
+     * 支付宝	ALIPAY	付款码25 26 27 28 29 30开头
+     * 银联	    UQRCODEPAY	付款码62开头
+     * 数币人民币	DCPAY	付款码01开头
+     * 翼支付	BESTPAY	付款码51开头
+     * 苏宁	    SUNING	付款码83开头
+     */
+    protected $authCode;
+    // 金额	M	String(12)	单位分,整数型字符
+    protected $totalAmount;
+    /**
+     * 地址位置信息	M	Object	地址位置信息,风控要求必送
+     * 
+     * request_ip	请求方IP地址	M	String(64)	请求方的IP地址,存在必填,格式如36.45.36.95
+     * base_station	基站信息	C	String(128)	客户端设备的基站信息(主扫时基站信息使用该字段)
+     * location	纬度,经度	C	String(32)	商户终端的地理位置,整体格式:纬度,经度,+表示北纬、东经,-表示南纬、 西经。
+     *                              经度格式:1位正负号+3位整数+1位小数点+5位小数;
+     *                              纬度格式:1位正负号+2位整数+1位小数点+6位小数;
+     *                              举例:+31.221345,+121.12345
+     */
+    protected $locationInfo;
+    // 业务模式	C	String(8)	业务模式: ACQ-收单 不填,默认为“ACQ-收单”
+    protected $busiMode;
+    // 订单标题	C	String(42)	标题,用于简单描述订单或商品(账户端控制,实际最多42个字符),微信支付必送。
+    protected $subject;
+    // 拉卡拉支付业务订单号	C	String(64)	拉卡拉订单系统订单号,以拉卡拉支付业务订单号为驱动的支付行为,需上传该字段。 订单交易下单,交易时上送订单系统订单号,交易流程中会校验有效性、判重
+    protected $payOrderNo;
+    // 商户通知地址	C	String(128)	商户通知地址,如上传,且 pay_order_no 不存在情况下,且支付响应报文是交易中状态的场景下,则按此地址通知商户
+    protected $notifyUrl;
+    // 结算类型	C	String(1)	“0”或者空,常规结算方式;
+    protected $settleType;
+    // 备注	C	String(128)
+    protected $remark;
+    // 扫码类型	C	String(1)	0或不填:扫码支付 1:支付宝刷脸支付;2: 微信刷脸支付
+    protected $scanType;
+    // 营销信息	C	String(1024)	拉卡拉优惠相关信息,JSON格式。暂不支持
+    protected $promoInfo;
+    // 账户端业务信息域	C	Object	参见以下acc_busi_fields字段详细说明,不同类型的auth_code对应不同的账户端,需要填写不同的信息
+    protected $accBusiFields;
+    // 商户订单号	C	String(32)	商品订单号,如动态码关联的某个商品订单号,每个外部订单来源下的外部商户订单号不可重复
+    protected $outOrderNo;
+    // 服务商机构标识码	C	String(11)	银联分配的服务商机构标识码
+    protected $pnrInsIdCd;
+
+    public function __construct()
+    {
+        parent::__construct();
+
+        $this->busiMode = 'ACQ';
+    }
+    
+    public function setMerchantNo($merchantNo)
+    {
+        $this->merchantNo = $merchantNo;
+        return $this;
+    }
+    
+    public function getMerchantNo()
+    {
+        return $this->merchantNo;
+    }
+    
+    public function setTermNo($termNo)
+    {
+        $this->termNo = $termNo;
+        return $this;
+    }
+    
+    public function getTermNo()
+    {
+        return $this->termNo;
+    }
+    
+    public function setOutTradeNo($outTradeNo)
+    {
+        $this->outTradeNo = $outTradeNo;
+        return $this;
+    }
+    
+    public function getOutTradeNo()
+    {
+        return $this->outTradeNo;
+    }
+    
+    public function setAuthCode($authCode)
+    {
+        $this->authCode = $authCode;
+        return $this;
+    }
+    
+    public function getAuthCode()
+    {
+        return $this->authCode;
+    }
+    
+    public function setTotalAmount($totalAmount)
+    {
+        $this->totalAmount = $totalAmount;
+        return $this;
+    }
+    
+    public function getTotalAmount()
+    {
+        return $this->totalAmount;
+    }
+    
+    public function setLocationInfo($locationInfo)
+    {
+        $this->locationInfo = $locationInfo;
+        return $this;
+    }
+    
+    public function getLocationInfo()
+    {
+        return $this->locationInfo;
+    }
+    
+    public function setBusiMode($busiMode)
+    {
+        $this->busiMode = $busiMode;
+        return $this;
+    }
+    
+    public function getBusiMode()
+    {
+        return $this->busiMode;
+    }
+    
+    public function setSubject($subject)
+    {
+        $this->subject = $subject;
+        return $this;
+    }
+    
+    public function getSubject()
+    {
+        return $this->subject;
+    }
+    
+    public function setPayOrderNo($payOrderNo)
+    {
+        $this->payOrderNo = $payOrderNo;
+        return $this;
+    }
+    
+    public function getPayOrderNo()
+    {
+        return $this->payOrderNo;
+    }
+    
+    public function setNotifyUrl($notifyUrl)
+    {
+        $this->notifyUrl = $notifyUrl;
+        return $this;
+    }
+    
+    public function getNotifyUrl()
+    {
+        return $this->notifyUrl;
+    }
+    
+    public function setSettleType($settleType)
+    {
+        $this->settleType = $settleType;
+        return $this;
+    }
+    
+    public function getSettleType()
+    {
+        return $this->settleType;
+    }
+    
+    public function setRemark($remark)
+    {
+        $this->remark = $remark;
+        return $this;
+    }
+    
+    public function getRemark()
+    {
+        return $this->remark;
+    }
+    
+    public function setScanType($scanType)
+    {
+        $this->scanType = $scanType;
+        return $this;
+    }
+    
+    public function getScanType()
+    {
+        return $this->scanType;
+    }
+    
+    public function setPromoInfo($promoInfo)
+    {
+        $this->promoInfo = $promoInfo;
+        return $this;
+    }
+    
+    public function getPromoInfo()
+    {
+        return $this->promoInfo;
+    }
+    
+    public function setAccBusiFields($accBusiFields)
+    {
+        $this->accBusiFields = $accBusiFields;
+        return $this;
+    }
+    
+    public function getAccBusiFields()
+    {
+        return $this->accBusiFields;
+    }
+    
+    public function setOutOrderNo($outOrderNo)
+    {
+        $this->outOrderNo = $outOrderNo;
+        return $this;
+    }
+    
+    public function getOutOrderNo()
+    {
+        return $this->outOrderNo;
+    }
+
+    public function setPnrInsIdCd($pnrInsIdCd)
+    {
+        $this->pnrInsIdCd = $pnrInsIdCd;
+        return $this;
+    }
+    
+    public function getPnrInsIdCd()
+    {
+        return $this->pnrInsIdCd;
+    }
+
+    /**
+     * Show all the invalid properties with reasons.
+     *
+     * @return array invalid properties with reasons
+     */
+    public function listInvalidProperties()
+    {
+        $invalidProperties = [];
+        if (strlen($this->merchantNo)===0) $invalidProperties[] = '商户号不能为空';
+        if (strlen($this->termNo)===0) $invalidProperties[] = '终端号不能为空';
+        if (strlen($this->outTradeNo)===0) $invalidProperties[] = '商户交易流水号不能为空';
+        if (strlen($this->authCode)===0) $invalidProperties[] = '支付授权码不能为空';
+        if (strlen($this->totalAmount)===0) $invalidProperties[] = '金额不能为空';
+        if ($this->locationInfo == null) $invalidProperties[] = '地址位置信息不能为空';
+
+        return $invalidProperties;
+    }
+
+    public function jsonSerialize()
+    {
+        $this->setReqData([
+            'merchant_no' => $this->merchantNo,
+            'term_no' => $this->termNo,
+            'out_trade_no' => $this->outTradeNo,
+            'auth_code' => $this->authCode,
+            'total_amount' => $this->totalAmount,
+            'location_info' => $this->locationInfo === null ? $this->locationInfo : $this->locationInfo->jsonSerialize(),
+            'busi_mode' => $this->busiMode,
+            'subject' => $this->subject,
+            'pay_order_no' => $this->payOrderNo,
+            'notify_url' => $this->notifyUrl,
+            'settle_type' => $this->settleType,
+            'remark' => $this->remark,
+            'scan_type' => $this->scanType,
+            'promo_info' => $this->promoInfo,
+            'acc_busi_fields' => $this->accBusiFields === null ? $this->accBusiFields : $this->accBusiFields->jsonSerialize(),
+            'out_order_no' => $this->outOrderNo,
+            'pnr_ins_id_cd' => $this->pnrInsIdCd,
+        ]);
+        return parent::jsonSerialize();
+    }
+}

+ 268 - 0
src/OpenAPISDK/V3/Model/TransMicropayResponse.php

@@ -0,0 +1,268 @@
+<?php
+/**
+ * TransMicropayResponse
+ * PHP version 7.4
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK\V3\Model
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+
+namespace SixShop\Lakala\OpenAPISDK\V3\Model;
+
+class TransMicropayResponse extends ModelResponse implements \JsonSerializable
+{
+    // 是否需要发起查询	M	String(32)	0=不需要 1=需要 当返回1时,代表订单处理中,商户需主动发起查询
+    protected $needQuery;
+    // 商户号	M	String(32)	拉卡拉分配的商户号(请求接口中商户号)
+    protected $merchantNo;
+    // 商户交易流水号	M	String(32)	请求报文中的商户交易流水号
+    protected $outTradeNo;
+    // 拉卡拉交易流水号	M	String(32)	拉卡拉交易流水号
+    protected $tradeNo;
+    // 拉卡拉对账单流水号	M	String(14)	拉卡拉对账单流水号
+    protected $logNo;
+    // 账户端交易订单号	C	String(48)	账户端交易流水号
+    protected $accTradeNo;
+    /**
+      * 钱包类型	M	String(16)
+      * 微信        WECHAT
+      * 支付宝      ALIPAY
+      * 银联        UQRCODEPAY
+      * 翼支付      BESTPAY
+      * 苏宁易付宝   SUNING
+      * 数字货币    DCPAY
+      */
+    protected $accountType;
+    // 订单金额	M	String(12)	单位分,整数数字型字符 订单金额=付款人实际发生金额+商户优惠金额+账户端优惠金额
+    protected $totalAmount;
+    // 付款人实际发生金额	M	String(12)
+    protected $payerAmount;
+    // 账户端应结订单金额	M	String(12)	应结订单金额,单位分 ,账户端应结订单金额=付款人实际发生金额+账户端优惠金额
+    protected $accSettleAmount;
+    // 商户优惠金额(账户端)	C	String(12)	账户端返回商户优惠金额,单位分
+    protected $accMdiscountAmount;
+    // 账户端优惠金额	C	String(12)	账户端返回账户端优惠金额,单位分
+    protected $accDiscountAmount;
+    // 账户端其它优惠金额	C	String(12)	账户端返回账户端其它优惠金额,单位分
+    protected $accOtherDiscountAmount;
+    // 交易完成时间	M	String(14)	以账户端返回时间为准
+    protected $tradeTime;
+    // 付款银行	C	String(128)	付款银行
+    protected $bankType;
+    /**
+     * 银行卡类型	C	String(16)	
+     * 00:借记
+     * 01:贷记
+     * 02:微信零钱
+     * 03:支付宝花呗
+     * 04:支付宝其他
+     * 05:数字货币
+     * 06:拉卡拉支付账户
+     * 99:未知
+     */
+    protected $cardType;
+    // 备注	C	String(128)
+    protected $remark;
+    // 账户端返回信息域	C	Object	账户端返回信息域
+    protected $accRespFields;
+    
+    public function setNeedQuery($needQuery)
+    {
+        $this->needQuery = $needQuery;
+        return $this;
+    }
+    
+    public function getNeedQuery()
+    {
+        return $this->needQuery;
+    }
+    
+    public function setMerchantNo($merchantNo)
+    {
+        $this->merchantNo = $merchantNo;
+        return $this;
+    }
+    
+    public function getMerchantNo()
+    {
+        return $this->merchantNo;
+    }
+    
+    public function setOutTradeNo($outTradeNo)
+    {
+        $this->outTradeNo = $outTradeNo;
+        return $this;
+    }
+    
+    public function getOutTradeNo()
+    {
+        return $this->outTradeNo;
+    }
+    
+    public function setTradeNo($tradeNo)
+    {
+        $this->tradeNo = $tradeNo;
+        return $this;
+    }
+    
+    public function getTradeNo()
+    {
+        return $this->tradeNo;
+    }
+    
+    public function setLogNo($logNo)
+    {
+        $this->logNo = $logNo;
+        return $this;
+    }
+    
+    public function getLogNo()
+    {
+        return $this->logNo;
+    }
+    
+    public function setAccTradeNo($accTradeNo)
+    {
+        $this->accTradeNo = $accTradeNo;
+        return $this;
+    }
+    
+    public function getAccTradeNo()
+    {
+        return $this->accTradeNo;
+    }
+    
+    public function setAccountType($accountType)
+    {
+        $this->accountType = $accountType;
+        return $this;
+    }
+    
+    public function getAccountType()
+    {
+        return $this->accountType;
+    }
+    
+    public function setTotalAmount($totalAmount)
+    {
+        $this->totalAmount = $totalAmount;
+        return $this;
+    }
+    
+    public function getTotalAmount()
+    {
+        return $this->totalAmount;
+    }
+    
+    public function setPayerAmount($payerAmount)
+    {
+        $this->payerAmount = $payerAmount;
+        return $this;
+    }
+    
+    public function getPayerAmount()
+    {
+        return $this->payerAmount;
+    }
+    
+    public function setAccSettleAmount($accSettleAmount)
+    {
+        $this->accSettleAmount = $accSettleAmount;
+        return $this;
+    }
+    
+    public function getAccSettleAmount()
+    {
+        return $this->accSettleAmount;
+    }
+    
+    public function setAccMdiscountAmount($accMdiscountAmount)
+    {
+        $this->accMdiscountAmount = $accMdiscountAmount;
+        return $this;
+    }
+    
+    public function getAccMdiscountAmount()
+    {
+        return $this->accMdiscountAmount;
+    }
+    
+    public function setAccDiscountAmount($accDiscountAmount)
+    {
+        $this->accDiscountAmount = $accDiscountAmount;
+        return $this;
+    }
+    
+    public function getAccDiscountAmount()
+    {
+        return $this->accDiscountAmount;
+    }
+    
+    public function setAccOtherDiscountAmount($accOtherDiscountAmount)
+    {
+        $this->accOtherDiscountAmount = $accOtherDiscountAmount;
+        return $this;
+    }
+    
+    public function getAccOtherDiscountAmount()
+    {
+        return $this->accOtherDiscountAmount;
+    }
+    
+    public function setTradeTime($tradeTime)
+    {
+        $this->tradeTime = $tradeTime;
+        return $this;
+    }
+    
+    public function getTradeTime()
+    {
+        return $this->tradeTime;
+    }
+    
+    public function setBankType($bankType)
+    {
+        $this->bankType = $bankType;
+        return $this;
+    }
+    
+    public function getBankType()
+    {
+        return $this->bankType;
+    }
+    
+    public function setCardType($cardType)
+    {
+        $this->cardType = $cardType;
+        return $this;
+    }
+    
+    public function getCardType()
+    {
+        return $this->cardType;
+    }
+    
+    public function setRemark($remark)
+    {
+        $this->remark = $remark;
+        return $this;
+    }
+    
+    public function getRemark()
+    {
+        return $this->remark;
+    }
+    
+    public function setAccRespFields($accRespFields)
+    {
+        $this->accRespFields = $accRespFields;
+        return $this;
+    }
+    
+    public function getAccRespFields()
+    {
+        return $this->accRespFields;
+    }
+}

+ 320 - 0
src/OpenAPISDK/V3/Model/TransPreorderRequest.php

@@ -0,0 +1,320 @@
+<?php
+/**
+ * 主扫交易 请求字段
+ * TransPreorderRequest
+ * PHP version 7.4
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK\V3\Model
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+
+namespace SixShop\Lakala\OpenAPISDK\V3\Model;
+
+class TransPreorderRequest extends ModelRequest implements \JsonSerializable
+{
+    // 商户号	M	String(32)	拉卡拉分配的商户号
+    protected $merchantNo;
+    // 终端号	M	String(32)	拉卡拉分配的业务终端号
+    protected $termNo;
+    // 商户交易流水号	M	String(32)	商户系统唯一,对应数据库表中外部请求流水号。
+    protected $outTradeNo;
+    /**
+     * 钱包类型	M	String(32)	
+     * 
+     * 微信             WECHAT
+     * 支付宝           ALIPAY
+     * 银联             UQRCODEPAY     默认
+     * 翼支付           BESTPAY
+     * 苏宁易付宝        SUNING 
+     * 拉卡拉支付账户     LKLACC 
+     * 网联小钱包        NUCSPAY
+     */
+    protected $accountType;
+    /**
+     * 接入方式	M	String(2)
+     * 
+     * 41   NATIVE((ALIPAY,云闪付支付,京东白条分期)
+     * 51   JSAPI(微信公众号支付,支付宝服务窗支付,银联JS支付,翼支付JS支付、拉卡拉钱包支付)
+     * 71   微信小程序支付
+     * 61   APP支付
+     */
+    protected $transType;
+    // 金额	M	String(12)	单位分,整数型字符
+    protected $totalAmount;
+    /**
+     * 地址位置信息	M	Object	地址位置信息,风控要求必送
+     * 
+     * request_ip	请求方IP地址	M	String(64)	请求方的IP地址,存在必填,格式如36.45.36.95
+     * base_station	基站信息	C	String(128)	客户端设备的基站信息(主扫时基站信息使用该字段)
+     * location	纬度,经度	C	String(32)	商户终端的地理位置,整体格式:纬度,经度,+表示北纬、东经,-表示南纬、 西经。
+     *                              经度格式:1位正负号+3位整数+1位小数点+5位小数;
+     *                              纬度格式:1位正负号+2位整数+1位小数点+6位小数;
+     *                              举例:+31.221345,+121.12345
+     */
+    protected $locationInfo;
+    // 业务模式	C	String(8)	业务模式: ACQ-收单 不填,默认为“ACQ-收单”
+    protected $busiMode;
+    // 订单标题	C	String(42)	标题,用于简单描述订单或商品主题,会传递给账户端 (账户端控制,实际最多42个字符),微信支付必送。
+    protected $subject;
+    // 支付业务订单号	C	String(64)	拉卡拉订单系统订单号,以拉卡拉支付业务订单号为驱动的支付行为,需上传该字段。
+    protected $payOrderNo;
+    // 商户通知地址	C	String(128)	商户通知地址,如果上传,且 pay_order_no 不存在情况下,则按此地址通知商户(详见“[交易通知]”接口)
+    protected $notifyUrl;
+    // 结算类型	C	String(1)	“0”或者空,常规结算方式,如需接拉卡拉分账通需传“1”,商户未开通分账之前切记不用上送此参数。;
+    protected $settleType;
+    // 备注	C	String(128)
+    protected $remark;
+    // 优惠信息	C	String(1024)	拉卡拉优惠相关信息,JSON格式。暂不支持
+    protected $promoInfo;
+    // 账户端业务信息域	C	Object	参见以下acc_busi_fields字段详细说明,不同的account_type和trans_type,需要传入的参数不一样
+    protected $accBusiFields;
+    // 商户订单号	C	String(32)	商品订单号,如动态码关联的某个商品订单号,每个外部订单来源下的外部商户订单号不可重复
+    protected $outOrderNo;
+    // 服务商机构标识码	C	String(11)	银联分配的服务商机构标识码
+    protected $pnrInsIdCd;
+
+    public function __construct()
+    {
+        parent::__construct();
+
+        $this->accountType = 'UQRCODEPAY';
+        $this->transType = '41';
+        $this->busiMode = 'ACQ';
+    }
+
+    public function setMerchantNo($merchantNo)
+    {
+        $this->merchantNo = $merchantNo;
+        return $this;
+    }
+
+    public function getMerchantNo()
+    {
+        return $this->merchantNo;
+    }
+
+    public function setTermNo($termNo)
+    {
+        $this->termNo = $termNo;
+        return $this;
+    }
+
+    public function getTermNo()
+    {
+        return $this->termNo;
+    }
+
+    public function setOutTradeNo($outTradeNo)
+    {
+        $this->outTradeNo = $outTradeNo;
+        return $this;
+    }
+
+    public function getOutTradeNo()
+    {
+        return $this->outTradeNo;
+    }
+
+    public function setAccountType($accountType)
+    {
+        $this->accountType = $accountType;
+        return $this;
+    }
+
+    public function getAccountType()
+    {
+        return $this->accountType;
+    }
+
+    public function setTransType($transType)
+    {
+        $this->transType = $transType;
+        return $this;
+    }
+
+    public function getTransType()
+    {
+        return $this->transType;
+    }
+
+    public function setTotalAmount($totalAmount)
+    {
+        $this->totalAmount = $totalAmount;
+        return $this;
+    }
+
+    public function getTotalAmount()
+    {
+        return $this->totalAmount;
+    }
+
+    public function setLocationInfo($locationInfo)
+    {
+        $this->locationInfo = $locationInfo;
+        return $this;
+    }
+
+    public function getLocationInfo()
+    {
+        return $this->locationInfo;
+    }
+
+    public function setBusiMode($busiMode)
+    {
+        $this->busiMode = $busiMode;
+        return $this;
+    }
+
+    public function getBusiMode()
+    {
+        return $this->busiMode;
+    }
+
+    public function setSubject($subject)
+    {
+        $this->subject = $subject;
+        return $this;
+    }
+
+    public function getSubject()
+    {
+        return $this->subject;
+    }
+
+    public function setPayOrderNo($payOrderNo)
+    {
+        $this->payOrderNo = $payOrderNo;
+        return $this;
+    }
+
+    public function getPayOrderNo()
+    {
+        return $this->payOrderNo;
+    }
+
+    public function setNotifyUrl($notifyUrl)
+    {
+        $this->notifyUrl = $notifyUrl;
+        return $this;
+    }
+
+    public function getNotifyUrl()
+    {
+        return $this->notifyUrl;
+    }
+
+    public function setSettleType($settleType)
+    {
+        $this->settleType = $settleType;
+        return $this;
+    }
+
+    public function getSettleType()
+    {
+        return $this->settleType;
+    }
+
+    public function setRemark($remark)
+    {
+        $this->remark = $remark;
+        return $this;
+    }
+
+    public function getRemark()
+    {
+        return $this->remark;
+    }
+
+    public function setPromoInfo($promoInfo)
+    {
+        $this->promoInfo = $promoInfo;
+        return $this;
+    }
+
+    public function getPromoInfo()
+    {
+        return $this->promoInfo;
+    }
+
+    public function setAccBusiFields($accBusiFields)
+    {
+        $this->accBusiFields = $accBusiFields;
+        return $this;
+    }
+
+    public function getAccBusiFields()
+    {
+        return $this->accBusiFields;
+    }
+
+    public function setOutOrderNo($outOrderNo)
+    {
+        $this->outOrderNo = $outOrderNo;
+        return $this;
+    }
+
+    public function getOutOrderNo()
+    {
+        return $this->outOrderNo;
+    }
+
+    public function setPnrInsIdCd($pnrInsIdCd)
+    {
+        $this->pnrInsIdCd = $pnrInsIdCd;
+        return $this;
+    }
+    
+    public function getPnrInsIdCd()
+    {
+        return $this->pnrInsIdCd;
+    }
+
+    /**
+     * Show all the invalid properties with reasons.
+     *
+     * @return array invalid properties with reasons
+     */
+    public function listInvalidProperties()
+    {
+        $invalidProperties = [];
+        if (strlen($this->merchantNo)===0) $invalidProperties[] = '商户号不能为空';
+        if (strlen($this->termNo)===0) $invalidProperties[] = '终端号不能为空';
+        if (strlen($this->outTradeNo)===0) $invalidProperties[] = '商户交易流水号不能为空';
+        if (strlen($this->accountType)===0) $invalidProperties[] = '钱包类型不能为空';
+        if (strlen($this->transType)===0) $invalidProperties[] = '接入方式接入方式不能为空';
+        if (strlen($this->totalAmount)===0) $invalidProperties[] = '金额不能为空';
+        if ($this->locationInfo == null) $invalidProperties[] = '地址位置信息不能为空';
+        if ($this->accountType === 'WECHAT' && strlen($this->subject) === 0) {
+            $invalidProperties[] = '微信支付必需上送订单标题字段[subject]';
+        }
+
+        return $invalidProperties;
+    }
+
+    #[\ReturnTypeWillChange]
+    public function jsonSerialize(): mixed
+    {
+        $this->setReqData([
+            'merchant_no' => $this->merchantNo,
+            'term_no' => $this->termNo,
+            'out_trade_no' => $this->outTradeNo,
+            'account_type' => $this->accountType,
+            'trans_type' => $this->transType,
+            'total_amount' => $this->totalAmount,
+            'location_info' => $this->locationInfo === null ? $this->locationInfo : $this->locationInfo->jsonSerialize(),
+            'busi_mode' => $this->busiMode,
+            'subject' => $this->subject,
+            'pay_order_no' => $this->payOrderNo,
+            'notify_url' => $this->notifyUrl,
+            'settle_type' => $this->settleType,
+            'remark' => $this->remark,
+            'promo_info' => $this->promoInfo,
+            'acc_busi_fields' => $this->accBusiFields === null ? $this->accBusiFields : $this->accBusiFields->jsonSerialize(),
+            'out_order_no' => $this->outOrderNo,
+            'pnr_ins_id_cd' => $this->pnrInsIdCd,
+        ]);
+        return parent::jsonSerialize();
+    }
+}

+ 107 - 0
src/OpenAPISDK/V3/Model/TransPreorderResponse.php

@@ -0,0 +1,107 @@
+<?php
+/**
+ * TransPreorderRequest
+ * PHP version 7.4
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK\V3\Model
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+
+namespace SixShop\Lakala\OpenAPISDK\V3\Model;
+
+class TransPreorderResponse extends ModelResponse implements \JsonSerializable
+{
+    // 商户号	M	String(32)	拉卡拉分配的商户号(请求接口中商户号)
+    protected $merchantNo;
+    // 商户请求流水号	M	String(32)	请求报文中的商户请求流水号
+    protected $outTradeNo;
+    // 拉卡拉交易流水号	M	String(32)	拉卡拉交易流水号
+    protected $tradeNo;
+    // 拉卡拉对账单流水号	M	String(14)	拉卡拉对账单流水号
+    protected $logNo;
+    // 结算商户号	M	String(32)	拉卡拉分配的商户号
+    protected $settleMerchantNo;
+    // 结算终端号	M	String(32)	拉卡拉分配的业务终端号
+    protected $settleTermNo;
+    // 账户端返回信息域	C	Object	账户端返回信息域
+    protected $accRespFields;
+
+    public function setMerchantNo($merchantNo)
+    {
+        $this->merchantNo = $merchantNo;
+        return $this;
+    }
+
+    public function getMerchantNo()
+    {
+        return $this->merchantNo;
+    }
+
+    public function setOutTradeNo($outTradeNo)
+    {
+        $this->outTradeNo = $outTradeNo;
+        return $this;
+    }
+
+    public function getOutTradeNo()
+    {
+        return $this->outTradeNo;
+    }
+
+    public function setTradeNo($tradeNo)
+    {
+        $this->tradeNo = $tradeNo;
+        return $this;
+    }
+
+    public function getTradeNo()
+    {
+        return $this->tradeNo;
+    }
+
+    public function setLogNo($logNo)
+    {
+        $this->logNo = $logNo;
+        return $this;
+    }
+
+    public function getLogNo()
+    {
+        return $this->logNo;
+    }
+
+    public function setSettleMerchantNo($settleMerchantNo)
+    {
+        $this->settleMerchantNo = $settleMerchantNo;
+        return $this;
+    }
+
+    public function getSettleMerchantNo()
+    {
+        return $this->settleMerchantNo;
+    }
+
+    public function setSettleTermNo($settleTermNo)
+    {
+        $this->settleTermNo = $settleTermNo;
+        return $this;
+    }
+
+    public function getSettleTermNo()
+    {
+        return $this->settleTermNo;
+    }
+
+    public function setAccRespFields($accRespFields)
+    {
+        $this->accRespFields = $accRespFields;
+        return $this;
+    }
+
+    public function getAccRespFields()
+    {
+        return $this->accRespFields;
+    }
+}

+ 67 - 0
src/OpenAPISDK/V3/ObjectSerializer.php

@@ -0,0 +1,67 @@
+<?php
+/**
+ * ObjectSerializer
+ * PHP version 7.4
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+
+namespace SixShop\Lakala\OpenAPISDK\V3;
+
+class ObjectSerializer
+{
+    public static function sanitizeForSerialization($data)
+    {
+        if (is_array($data)) {
+            foreach ($data as $property => $value) {
+                $data[$property] = self::sanitizeForSerialization($value);
+            }
+            return $data;
+        } elseif (is_object($data)) {
+            $values = [];
+            foreach (get_object_vars($data) as $property => $value) {
+                $values[$property] = self::sanitizeForSerialization($value);
+            }
+            return $values;
+        } else {
+            return $data;
+        }
+    }
+
+    public static function deserialize($data, $class, $headers = null)
+    {
+        if ($class === '\DateTime') {
+            return new \DateTime($data);
+        } elseif (in_array($class, ['bool', 'boolean', 'int', 'integer', 'float', 'double', 'string', 'array'])) {
+            settype($data, $class);
+            return $data;
+        } else {
+            $instance = new $class();
+            foreach ($data as $property => $value) {
+                $camelProp = str_replace(' ', '', ucwords(str_replace('_', ' ', $property)));
+                $setter = 'set' . $camelProp;
+                if (method_exists($instance, $setter)) {
+                    $instance->$setter($value);
+                }
+            }
+            if ($headers && method_exists($instance, 'setHeaders')) {
+                $instance->setHeaders($headers);
+            }
+            return $instance;
+        }
+    }
+
+    # 此方法仅针对请求Model参数的对象json编码处理
+    public static function jsonencode($value)
+    {
+        if ($value === null || is_string($value)) return $value;
+        if (is_array($value)) return json_encode($value, JSON_UNESCAPED_UNICODE);
+        if (method_exists($value, 'jsonSerialize')) {
+            return json_encode($value->jsonSerialize(), JSON_UNESCAPED_UNICODE);
+        }
+        return json_encode($value, JSON_UNESCAPED_UNICODE);
+    }
+}

+ 163 - 0
src/OpenAPISDK/V3/Util/HttpService.php

@@ -0,0 +1,163 @@
+<?php
+/**
+ * HttpService
+ * PHP version 7.4
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK\V3\Util
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+
+namespace SixShop\Lakala\OpenAPISDK\V3\Util;
+
+/**
+ * HTTP请求服务
+ * Class HttpService
+ * @package service
+ */
+class HttpService
+{
+    // Response types
+    const RESPOND_TYPE_SIMPLE = 'simple';
+    const RESPOND_TYPE_ORIGINAL = 'original';
+    const RESPOND_TYPE_ARRAY = 'array';
+    const RESPOND_TYPE_JSON = 'json';
+
+    /**
+     * Send a GET request
+     * @param string $url HTTP request URL
+     * @param array $query GET request parameters
+     * @param array $options CURL options
+     * @return mixed
+     */
+    public static function get($url, array $query = [], array $options = [])
+    {
+        $options['query'] = $query;
+        return self::request('GET', $url, $options);
+    }
+
+    /**
+     * Send a POST request
+     * @param string $url HTTP request URL
+     * @param array $data POST request data
+     * @param array $options CURL options
+     * @return mixed
+     */
+    public static function post($url, array $data = [], array $options = [])
+    {
+        $options['data'] = $data;
+        return self::request('POST', $url, $options);
+    }
+
+    /**
+     * Send a HTTP request
+     * @param string $method Request method
+     * @param string $url Request URL
+     * @param array $options Request options [header, data, ssl_cer, ssl_key, respond_type]
+     * @return mixed
+     */
+    public static function request($method, $url, array $options = [])
+    {
+        $curl = curl_init();
+// print_r($options);
+        // Set GET parameters
+        if (!empty($options['query'])) {
+            $url .= (strpos($url, '?') === false ? '?' : '&') . http_build_query($options['query']);
+        }
+
+        // Set POST data
+        if (strtoupper($method) === 'POST') {
+            curl_setopt($curl, CURLOPT_POST, true);
+            curl_setopt($curl, CURLOPT_POSTFIELDS, $options['data']);
+        }
+
+        // Set request timeout
+        curl_setopt($curl, CURLOPT_TIMEOUT, $options['timeout'] ?? 60);
+
+        // Set response type
+        $validRespondTypes = [self::RESPOND_TYPE_SIMPLE, self::RESPOND_TYPE_ORIGINAL, self::RESPOND_TYPE_ARRAY, self::RESPOND_TYPE_JSON];
+        $respondType = in_array($options['respond_type'] ?? '', $validRespondTypes) ? $options['respond_type'] : self::RESPOND_TYPE_SIMPLE;
+
+        // Set headers
+        if (!empty($options['header'])) {
+            curl_setopt($curl, CURLOPT_HTTPHEADER, $options['header']);
+        }
+
+        // Set SSL certificates
+        if (!empty($options['ssl_cer']) && file_exists($options['ssl_cer'])) {
+            curl_setopt($curl, CURLOPT_SSLCERTTYPE, 'PEM');
+            curl_setopt($curl, CURLOPT_SSLCERT, $options['ssl_cer']);
+        }
+        if (!empty($options['ssl_key']) && file_exists($options['ssl_key'])) {
+            curl_setopt($curl, CURLOPT_SSLKEYTYPE, 'PEM');
+            curl_setopt($curl, CURLOPT_SSLKEY, $options['ssl_key']);
+        }
+
+        // Set common options
+        curl_setopt($curl, CURLOPT_URL, $url);
+        curl_setopt($curl, CURLOPT_HEADER, $respondType !== self::RESPOND_TYPE_SIMPLE);
+        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
+        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
+        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
+
+        $content = curl_exec($curl);
+        $curlInfo = curl_getinfo($curl);
+
+        // Remove BOM header
+        $content = preg_replace('/^\xEF\xBB\xBF/', '', $content);
+
+        $response = self::formatResponse($content, $curlInfo, $respondType);
+        curl_close($curl);
+        return $response;
+    }
+
+    /**
+     * Format response based on respond type
+     * @param string $content
+     * @param array $curlInfo
+     * @param string $respondType
+     * @return mixed
+     */
+    private static function formatResponse($content, $curlInfo, $respondType)
+    {
+        switch ($respondType) {
+            case self::RESPOND_TYPE_ORIGINAL:
+                return $content;
+            case self::RESPOND_TYPE_ARRAY:
+            case self::RESPOND_TYPE_JSON:
+                $headerSize = $curlInfo['header_size'];
+                $header = substr($content, 0, $headerSize);
+                $body = substr($content, $headerSize);
+                $response = [
+                    'body' => $body,
+                    'content' => json_decode($body),
+                    'info' => $curlInfo,
+                    'header' => self::parseResponseHeader($header)
+                ];
+                return $respondType === self::RESPOND_TYPE_JSON ? json_encode($response, JSON_UNESCAPED_UNICODE) : $response;
+            default:
+                return $content;
+        }
+    }
+
+    /**
+     * Parse response header
+     * @param string $header
+     * @return array
+     */
+    private static function parseResponseHeader($header)
+    {
+        $headers = [];
+        $headerLines = explode("\r\n", $header);
+        $headers['status'] = $headerLines[0];
+        array_shift($headerLines);
+        foreach ($headerLines as $line) {
+            if (strpos($line, ':') !== false) {
+                list($key, $value) = explode(':', $line, 2);
+                $headers[ucwords(trim($key), '-')] = trim($value);
+            }
+        }
+        return $headers;
+    }
+}

+ 201 - 0
src/OpenAPISDK/V3/Util/LakalaSM4.php

@@ -0,0 +1,201 @@
+<?php
+/**
+ * LakalaSM4
+ * PHP version 7.4
+ *
+ * @category Class
+ * @package  Lakala\OpenAPISDK\V3\Util
+ * @author   lucongyu
+ * @link     https://o.lakala.com
+ */
+
+namespace SixShop\Lakala\OpenAPISDK\V3\Util;
+
+class LakalaSM4
+{
+    const SM4_CK = [
+        0x00070e15, 0x1c232a31, 0x383f464d, 0x545b6269,
+        0x70777e85, 0x8c939aa1, 0xa8afb6bd, 0xc4cbd2d9,
+        0xe0e7eef5, 0xfc030a11, 0x181f262d, 0x343b4249,
+        0x50575e65, 0x6c737a81, 0x888f969d, 0xa4abb2b9,
+        0xc0c7ced5, 0xdce3eaf1, 0xf8ff060d, 0x141b2229,
+        0x30373e45, 0x4c535a61, 0x686f767d, 0x848b9299,
+        0xa0a7aeb5, 0xbcc3cad1, 0xd8dfe6ed, 0xf4fb0209,
+        0x10171e25, 0x2c333a41, 0x484f565d, 0x646b7279
+    ];
+
+    const SM4_SBOX = [
+        0xd6,0x90,0xe9,0xfe,0xcc,0xe1,0x3d,0xb7,0x16,0xb6,0x14,0xc2,0x28,0xfb,0x2c,0x05,
+        0x2b,0x67,0x9a,0x76,0x2a,0xbe,0x04,0xc3,0xaa,0x44,0x13,0x26,0x49,0x86,0x06,0x99,
+        0x9c,0x42,0x50,0xf4,0x91,0xef,0x98,0x7a,0x33,0x54,0x0b,0x43,0xed,0xcf,0xac,0x62,
+        0xe4,0xb3,0x1c,0xa9,0xc9,0x08,0xe8,0x95,0x80,0xdf,0x94,0xfa,0x75,0x8f,0x3f,0xa6,
+        0x47,0x07,0xa7,0xfc,0xf3,0x73,0x17,0xba,0x83,0x59,0x3c,0x19,0xe6,0x85,0x4f,0xa8,
+        0x68,0x6b,0x81,0xb2,0x71,0x64,0xda,0x8b,0xf8,0xeb,0x0f,0x4b,0x70,0x56,0x9d,0x35,
+        0x1e,0x24,0x0e,0x5e,0x63,0x58,0xd1,0xa2,0x25,0x22,0x7c,0x3b,0x01,0x21,0x78,0x87,
+        0xd4,0x00,0x46,0x57,0x9f,0xd3,0x27,0x52,0x4c,0x36,0x02,0xe7,0xa0,0xc4,0xc8,0x9e,
+        0xea,0xbf,0x8a,0xd2,0x40,0xc7,0x38,0xb5,0xa3,0xf7,0xf2,0xce,0xf9,0x61,0x15,0xa1,
+        0xe0,0xae,0x5d,0xa4,0x9b,0x34,0x1a,0x55,0xad,0x93,0x32,0x30,0xf5,0x8c,0xb1,0xe3,
+        0x1d,0xf6,0xe2,0x2e,0x82,0x66,0xca,0x60,0xc0,0x29,0x23,0xab,0x0d,0x53,0x4e,0x6f,
+        0xd5,0xdb,0x37,0x45,0xde,0xfd,0x8e,0x2f,0x03,0xff,0x6a,0x72,0x6d,0x6c,0x5b,0x51,
+        0x8d,0x1b,0xaf,0x92,0xbb,0xdd,0xbc,0x7f,0x11,0xd9,0x5c,0x41,0x1f,0x10,0x5a,0xd8,
+        0x0a,0xc1,0x31,0x88,0xa5,0xcd,0x7b,0xbd,0x2d,0x74,0xd0,0x12,0xb8,0xe5,0xb4,0xb0,
+        0x89,0x69,0x97,0x4a,0x0c,0x96,0x77,0x7e,0x65,0xb9,0xf1,0x09,0xc5,0x6e,0xc6,0x84,
+        0x18,0xf0,0x7d,0xec,0x3a,0xdc,0x4d,0x20,0x79,0xee,0x5f,0x3e,0xd7,0xcb,0x39,0x48
+    ];
+
+    const SM4_FK = [0xA3B1BAC6, 0x56AA3350, 0x677D9197, 0xB27022DC];
+
+    public $_rk = [];
+    public $_block_size = 16;
+
+    public function __construct()
+    {
+    }
+
+    /**
+     * sm4加密(ecb)
+     * @param $key 16位十六进制的字符,比如asw34a5ses5w81wf
+     * @param $data 原始数据
+     * @return string
+     */
+    public function encrypt($key, $data)
+    {
+        $this->sm4KeySchedule($key);
+
+        $bytes = $this->pad($data, $this->_block_size);
+        $chunks = array_chunk($bytes, $this->_block_size);
+
+        $ciphertext = "";
+        foreach ($chunks as $chunk) {
+            $ciphertext .= $this->sm4Encrypt($chunk);
+        }
+
+        return base64_encode($ciphertext);
+    }
+
+    /**
+     * sm4解密
+     * @param $key
+     * @param $data
+     * @return bool|string
+     */
+    public function decrypt($key, $data)
+    {
+        $data = base64_decode($data);
+        if (strlen($data) < 0 || strlen($data) % $this->_block_size != 0) {
+            return false;
+        }
+
+        $this->sm4KeySchedule($key);
+        $bytes = unpack("C*", $data);
+        $chunks = array_chunk($bytes, $this->_block_size);
+
+        $plaintext = "";
+        foreach ($chunks as $chunk) {
+            $plaintext .= substr($this->sm4Decrypt($chunk), 0, 16);
+        }
+        $plaintext = $this->unPad($plaintext);
+
+        return $plaintext;
+    }
+
+    private function sm4Decrypt($cipherText)
+    {
+        $x = [];
+        for ($j=0; $j<4; $j++) {
+            $x[$j]=($cipherText[$j*4]<<24)  |($cipherText[$j*4+1]<<16)| ($cipherText[$j*4+2]<<8)|($cipherText[$j*4+3]);
+        }
+
+        for ($i=0; $i<32; $i++) {
+            $tmp = $x[$i+1]^$x[$i+2]^$x[$i+3]^$this->_rk[31-$i];
+            $buf= (self::SM4_SBOX[($tmp >> 24) & 0xFF]) << 24 |(self::SM4_SBOX[($tmp >> 16) & 0xFF]) << 16 |(self::SM4_SBOX[($tmp >> 8) & 0xFF]) << 8 |(self::SM4_SBOX[$tmp & 0xFF]);
+            $x[$i+4]=$x[$i]^($buf^$this->sm4Rotl32(($buf), 2)^ $this->sm4Rotl32(($buf), 10) ^ $this->sm4Rotl32(($buf), 18)^ $this->sm4Rotl32(($buf), 24));
+        }
+
+        $plainText = [];
+        for ($k=0; $k<4; $k++) {
+            $plainText[4*$k]=($x[35-$k]>> 24)& 0xFF;
+            $plainText[4*$k+1]=($x[35-$k]>> 16)& 0xFF;
+            $plainText[4*$k+2]=($x[35-$k]>> 8)& 0xFF;
+            $plainText[4*$k+3]=($x[35-$k])& 0xFF;
+        }
+
+        return $this->bytesToString($plainText);
+    }
+
+    private function sm4Encrypt($plainText)
+    {
+        $x = [];
+        for ($j=0; $j<4; $j++) {
+            $x[$j]=($plainText[$j*4]<<24)  |($plainText[$j*4+1]<<16)| ($plainText[$j*4+2]<<8)|($plainText[$j*4+3]);
+        }
+
+        for ($i=0; $i<32; $i++) {
+            $tmp = $x[$i+1]^$x[$i+2]^$x[$i+3]^$this->_rk[$i];
+            $buf= (self::SM4_SBOX[($tmp >> 24) & 0xFF]) << 24 |(self::SM4_SBOX[($tmp >> 16) & 0xFF]) << 16 |(self::SM4_SBOX[($tmp >> 8) & 0xFF]) << 8 |(self::SM4_SBOX[$tmp & 0xFF]);
+            $x[$i+4]=$x[$i]^($buf^$this->sm4Rotl32(($buf), 2)^ $this->sm4Rotl32(($buf), 10) ^ $this->sm4Rotl32(($buf), 18)^ $this->sm4Rotl32(($buf), 24));
+        }
+
+        $cipherText = [];
+        for ($k=0; $k<4; $k++) {
+            $cipherText[4*$k]=($x[35-$k]>> 24)& 0xFF;
+            $cipherText[4*$k+1]=($x[35-$k]>> 16)& 0xFF;
+            $cipherText[4*$k+2]=($x[35-$k]>> 8)& 0xFF;
+            $cipherText[4*$k+3]=($x[35-$k])& 0xFF;
+        }
+
+        return $this->bytesToString($cipherText);
+    }
+
+    private function stringToBytes($string)
+    {
+        return unpack('C*', $string);
+    }
+
+    private function bytesToString($bytes)
+    {
+        return vsprintf(str_repeat('%c', count($bytes)), $bytes);
+    }
+
+    private function pad($data)
+    {
+        $bytes = $this->stringToBytes($data);
+        $rem = $this->_block_size - count($bytes) % $this->_block_size;
+        for ($i = 0; $i < $rem; $i++) {
+            array_push($bytes, $rem);
+        }
+        return $bytes;
+    }
+
+    private function unPad($data)
+    {
+        $bytes = $this->stringToBytes($data);
+        $rem = $bytes[count($bytes)];
+        $bytes = array_slice($bytes, 0, count($bytes) - $rem);
+        return $this->bytesToString($bytes);
+    }
+
+    private function sm4Rotl32($buf, $n)
+    {
+        return (($buf << $n) & 0xffffffff) | ($buf >> (32-$n));
+    }
+
+    private function sm4KeySchedule($key)
+    {
+        $this->_rk = [];
+        $key = array_values(unpack("C*", $key));
+
+        $k = [];
+        for ($i=0; $i<4; $i++) {
+            $k[$i] = self::SM4_FK[$i]^(($key[4*$i]<<24) | ($key[4*$i+1]<<16) |($key[4*$i+2]<<8) | ($key[4*$i+3]));
+        }
+
+        for ($j=0; $j<32; $j++) {
+            $tmp = $k[$j+1]^$k[$j+2]^$k[$j+3]^ self::SM4_CK[$j];
+            $buf = (self::SM4_SBOX[($tmp >> 24) & 0xFF]) << 24 |(self::SM4_SBOX[($tmp >> 16) & 0xFF]) << 16 |(self::SM4_SBOX[($tmp >> 8) & 0xFF]) << 8 |(self::SM4_SBOX[$tmp & 0xFF]);
+
+            $k[$j+4]=$k[$j]^(($buf)^($this->sm4Rotl32(($buf), 13))^($this->sm4Rotl32(($buf), 23)));
+            $this->_rk[$j]=$k[$j+4];
+        }
+    }
+}

+ 128 - 0
src/Service/TransactionService.php

@@ -0,0 +1,128 @@
+<?php
+declare(strict_types=1);
+namespace SixShop\Lakala\Service;
+
+use SixShop\Lakala\OpenAPISDK\V3\Api\TransPreorderApi;
+use SixShop\Lakala\OpenAPISDK\V3\Configuration;
+use SixShop\Lakala\OpenAPISDK\V3\Model\TradePreorderWechaAccBusiFields;
+use SixShop\Lakala\OpenAPISDK\V3\Model\TradePreorderWechaDetail;
+use SixShop\Lakala\OpenAPISDK\V3\Model\TradePreorderWechaGoodsDetail;
+use SixShop\Lakala\OpenAPISDK\V3\Model\TransPreorderRequest;
+use SixShop\Lakala\Config;
+use SixShop\Lakala\Dto\LocationInfo;
+
+/**
+ * TransactionService
+ */
+class TransactionService
+{
+    private TransPreorderApi $transPreorderApi;
+    public function __construct(private Config $config)
+    {
+        $this->transPreorderApi = new TransPreorderApi($config);
+    }
+
+    /**
+     * 聚合主扫
+     * @param string $outTradeNo 商户交易流水号
+     * @param float $totalAmount 交易金额
+     * @param LocationInfo $locationInfo 地理位置信息
+     * @param string $accountType 账户类型 微信:WECHAT 支付宝:ALIPAY 银联:UQRCODEPAY 翼支付: BESTPAY 苏宁易付宝: SUNING 拉卡拉支付账户:LKLACC 网联小钱包:NUCSPAY 京东钱包:JD
+     * @param string $transType 交易类型 41:NATIVE((ALIPAY,云闪付支持,京东白条分期) 51:JSAPI(微信公众号支付,支付宝服务窗支付,银联JS支付,翼支付JS支付, 拉卡拉钱包支付, 京东白条分期) 71:微信小程序支付 61:APP支付(微信APP支付)
+     * @param string $busiMode 业务模式 ACQ-收单 不填,默认为“ACQ-收单”
+     * @param string $subject 交易标题
+     * @param string $payOrderNo 拉卡拉订单系统订单号,以拉卡拉支付业务订单号为驱动的支付行为,需上传该字段。
+     * @param string $settleType 结算类型 “0”或者空,常规结算方式,如需接拉卡拉分账通需传“1”,商户未开通分账之前切记不用上送此参数。
+     * @param string $remark 备注
+     * @param object $accBusiFields 账户端业务信息域
+     *
+     * @link https://o.lakala.com/#/home/document/detail?id=110
+     */
+    public function preOrder(
+        string $outTradeNo,
+        float $totalAmount,
+        LocationInfo $locationInfo,
+        string $accountType = 'WECHAT',
+        string $transType = '71',
+        string $busiMode = '',
+        string $subject = '',
+        string $payOrderNo = '',
+        string $settleType = '',
+        string $remark = '',
+        object $accBusiFields = null,
+    )
+    {
+        $request = new TransPreorderRequest();
+
+        $request->setMerchantNo($outTradeNo);
+        $request->setTermNo($this->config->term_no);
+        $request->setOutTradeNo(date('YmdHis'));
+        $request->setAccountType($accountType);
+        $request->setTransType($transType);
+        $totalAmount = round($totalAmount * 100, 0);
+        $request->setTotalAmount($totalAmount); // 单位分
+        $request->setLocationInfo($locationInfo);
+        $request->setSubject($subject);
+
+        // 非必填参数
+        $request->setBusiMode($busiMode);
+        $request->setPayOrderNo($payOrderNo);
+        $request->setNotifyUrl($this->config->notify_url);
+        $request->setSettleType($settleType);
+        $request->setRemark($remark);
+        // $request->setPromoInfo('');
+        // $request->setOutOrderNo('');
+        // $request->setPnrInsIdCd('');
+
+        // 微信主扫场景 - 账户端业务信息
+        $acc_busi_fields = new TradePreorderWechaAccBusiFields();
+        $acc_busi_fields->setTimeoutExpress('');
+        $acc_busi_fields->setSubAppid('');
+        $acc_busi_fields->setUserId('2843132323');
+        $acc_busi_fields->setDetail('');
+        $acc_busi_fields->setGoodsTag('');
+        $acc_busi_fields->setAttach('');
+        $acc_busi_fields->setDeviceInfo('');
+        $acc_busi_fields->setLimitPay('');
+        $acc_busi_fields->setSceneInfo('');
+        $acc_busi_fields->setLimitPayer('');
+
+        $detail = new TradePreorderWechaDetail();
+        $detail->setCostPrice('100');
+        $detail->setReceiptId('');
+
+        $goods_detail = new TradePreorderWechaGoodsDetail();
+        $goods_detail->setGoodsId('3452234');
+        $goods_detail->setWxpayGoodsId('');
+        $goods_detail->setGoodsName('');
+        $goods_detail->setQuantity('');
+        $goods_detail->setPrice('');
+
+        $detail->setGoodsDetail([$goods_detail]);
+
+        $acc_busi_fields->setDetail($detail);
+
+        // 账户端业务信息
+        $request->setAccBusiFields($acc_busi_fields);
+
+        try {
+            $response = $this->transPreorderApi->transPreorder($request);
+            if ($response->getRespData()) {
+                print_r($response->getRespData());
+                print_r($response->getAccRespFields());
+            }
+            else {
+                print_r($response);
+            }
+            echo $response->getCode();
+
+            # 响应头信息
+            print_r($response->getHeaders());
+
+            # 响应原文
+            echo $response->getOriginalText();
+        } catch (\Lakala\OpenAPISDK\V3\ApiException $e) {
+            echo $e->getMessage();
+        }
+    }
+}

+ 26 - 0
tests/Service/TransactionServiceTest.php

@@ -0,0 +1,26 @@
+<?php
+
+namespace SixShop\Lakala\Service;
+
+use PHPUnit\Framework\TestCase;
+use SixShop\Lakala\Dto\LocationInfo;
+use SixShop\Payment\Enum\NumberBizEnum;
+
+class TransactionServiceTest extends TestCase
+{
+    private TransactionService $transactionService;
+    protected function setUp(): void
+    {
+        $this->transactionService = app(TransactionService::class);
+    }
+
+    public function testPreOrder()
+    {
+        $this->transactionService->preOrder(
+            outTradeNo: generate_number(NumberBizEnum::ORDER_PAY),
+            totalAmount: .01,
+            locationInfo: new LocationInfo(requestIP: '127.0.0.1'),
+            subject: '测试订单',
+        );
+    }
+}