From 34474587d0da5d94b483f1260ab94f2081a1da1d Mon Sep 17 00:00:00 2001 From: mustapayev Date: Sat, 12 Oct 2024 13:06:29 +0200 Subject: [PATCH 1/7] estpos - fix issue #239 undefined index mdErrorMsg --- .../EstPosResponseDataMapper.php | 11 +++- .../EstPosResponseDataMapperTest.php | 58 +++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/src/DataMapper/ResponseDataMapper/EstPosResponseDataMapper.php b/src/DataMapper/ResponseDataMapper/EstPosResponseDataMapper.php index 5f9c3e47..535183aa 100644 --- a/src/DataMapper/ResponseDataMapper/EstPosResponseDataMapper.php +++ b/src/DataMapper/ResponseDataMapper/EstPosResponseDataMapper.php @@ -135,10 +135,19 @@ public function map3DPaymentData(array $raw3DAuthResponseData, ?array $rawPaymen 'eci' => null, 'tx_status' => null, 'cavv' => null, - 'md_error_message' => $this->is3dAuthSuccess($mdStatus) ? null : $raw3DAuthResponseData['mdErrorMsg'], + 'md_error_message' => null, '3d_all' => $raw3DAuthResponseData, ]; + if (null !== $mdStatus) { + if (!$this->is3dAuthSuccess($mdStatus)) { + $threeDResponse['md_error_message'] = $raw3DAuthResponseData['mdErrorMsg']; + } + } else { + $threeDResponse['error_code'] = $raw3DAuthResponseData['ErrorCode']; + $threeDResponse['error_message'] = $raw3DAuthResponseData['ErrMsg']; + } + if ($this->is3dAuthSuccess($mdStatus)) { $threeDResponse['eci'] = $raw3DAuthResponseData['eci']; $threeDResponse['cavv'] = $raw3DAuthResponseData['cavv']; diff --git a/tests/Unit/DataMapper/ResponseDataMapper/EstPosResponseDataMapperTest.php b/tests/Unit/DataMapper/ResponseDataMapper/EstPosResponseDataMapperTest.php index 5be978b7..ede214f3 100644 --- a/tests/Unit/DataMapper/ResponseDataMapper/EstPosResponseDataMapperTest.php +++ b/tests/Unit/DataMapper/ResponseDataMapper/EstPosResponseDataMapperTest.php @@ -505,6 +505,64 @@ public static function threeDPaymentDataProvider(): array 'installment_count' => 0, ], ], + '3d_auth_fail_wrong_card_number_format' => [ + 'order' => [ + 'currency' => PosInterface::CURRENCY_TRY, + 'amount' => 1.01, + ], + 'txType' => PosInterface::TX_TYPE_PAY_AUTH, + 'threeDResponseData' => [ + 'amount' => 0.01, + 'clientid' => '*', + 'currency' => '*', + 'Ecom_Payment_Card_ExpDate_Month' => 0, + 'Ecom_Payment_Card_ExpDate_Year' => 0, + 'ErrMsg' => 'Girilen kart numarası doğru formatta değildir. Kart numarasını kontrol ederek tekrar işlem deneyiniz.', + 'ErrorCode' => 'HPP-1001', + 'failUrl' => 'https://*.com/odeme/f05e81c8-4ea0-44a9-8fe8-d45b854c62d9', + 'HASH' => '**/fxNKZvC4E2EbQOgiqNi9FeXBMj636Q==', + 'hashAlgorithm' => 'ver3', + 'lang' => 'tr', + 'maskedCreditCard' => '***', + 'MaskedPan' => '**', + 'oid' => 'f05e81c8', + 'okUrl' => 'https://*.com/odeme/d45b854c62d9', + 'Response' => 'Error', + 'rnd' => 'MZrcwoSd1+-*', + 'storetype' => '3d', + 'taksit' => '', + 'traceId' => '****', + 'TranType' => 'Auth', + ], + 'paymentData' => [], + 'expectedData' => [ + 'amount' => 0.01, + 'auth_code' => null, + 'batch_num' => null, + 'cavv' => null, + 'currency' => '*', + 'eci' => null, + 'error_code' => 'HPP-1001', + 'error_message' => 'Girilen kart numarası doğru formatta değildir. Kart numarasını kontrol ederek tekrar işlem deneyiniz.', + 'installment_count' => 0, + 'masked_number' => '***', + 'md_error_message' => null, + 'md_status' => null, + 'month' => 0, + 'order_id' => 'f05e81c8', + 'payment_model' => '3d', + 'proc_return_code' => null, + 'ref_ret_num' => null, + 'status' => 'declined', + 'status_detail' => null, + 'transaction_id' => null, + 'transaction_security' => null, + 'transaction_type' => 'pay', + 'transaction_time' => null, + 'tx_status' => null, + 'year' => 0, + ], + ], '3d_auth_success_payment_fail' => [ 'order' => [ 'currency' => PosInterface::CURRENCY_TRY, From af3167648d38cc95b8726874ff64f2e4b2fb82db Mon Sep 17 00:00:00 2001 From: mustapayev Date: Sat, 12 Oct 2024 13:10:58 +0200 Subject: [PATCH 2/7] estpos - fix issue #237 amount must be of type string null given --- src/DataMapper/ResponseDataMapper/EstPosResponseDataMapper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DataMapper/ResponseDataMapper/EstPosResponseDataMapper.php b/src/DataMapper/ResponseDataMapper/EstPosResponseDataMapper.php index 535183aa..3ba9800d 100644 --- a/src/DataMapper/ResponseDataMapper/EstPosResponseDataMapper.php +++ b/src/DataMapper/ResponseDataMapper/EstPosResponseDataMapper.php @@ -129,7 +129,7 @@ public function map3DPaymentData(array $raw3DAuthResponseData, ?array $rawPaymen 'masked_number' => $raw3DAuthResponseData['maskedCreditCard'], 'month' => $raw3DAuthResponseData['Ecom_Payment_Card_ExpDate_Month'], 'year' => $raw3DAuthResponseData['Ecom_Payment_Card_ExpDate_Year'], - 'amount' => $this->formatAmount($raw3DAuthResponseData['amount']), + 'amount' => null !== $raw3DAuthResponseData['amount'] ? $this->formatAmount($raw3DAuthResponseData['amount']) : null, 'currency' => $this->mapCurrency($raw3DAuthResponseData['currency']), 'installment_count' => $this->mapInstallment($raw3DAuthResponseData['taksit']), 'eci' => null, From 391db45124282129dd4f1064fed1c0605bf74ce8 Mon Sep 17 00:00:00 2001 From: mustapayev Date: Sat, 12 Oct 2024 13:14:41 +0200 Subject: [PATCH 3/7] updated .editorconfig --- .editorconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/.editorconfig b/.editorconfig index 77226817..ae19070e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -20,6 +20,7 @@ ij_php_align_phpdoc_comments = true ij_php_space_after_type_cast = true ij_php_concat_spaces = false ij_php_phpdoc_param_spaces_between_tag_and_type = 1 +ij_php_comma_after_last_array_element = true [*.md] max_line_length = 80 From bb16c758d3b2391a444a7b677f7817097e770340 Mon Sep 17 00:00:00 2001 From: mustapayev Date: Sat, 12 Oct 2024 13:16:46 +0200 Subject: [PATCH 4/7] tests - working on test coverage --- .../EstPosRequestDataMapperTest.php | 12 ------------ .../EstPosResponseDataMapperTest.php | 8 +++++++- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/tests/Unit/DataMapper/RequestDataMapper/EstPosRequestDataMapperTest.php b/tests/Unit/DataMapper/RequestDataMapper/EstPosRequestDataMapperTest.php index 72343c90..225d5ab1 100644 --- a/tests/Unit/DataMapper/RequestDataMapper/EstPosRequestDataMapperTest.php +++ b/tests/Unit/DataMapper/RequestDataMapper/EstPosRequestDataMapperTest.php @@ -38,8 +38,6 @@ class EstPosRequestDataMapperTest extends TestCase /** @var EventDispatcherInterface & MockObject */ private EventDispatcherInterface $dispatcher; - private array $order; - protected function setUp(): void { parent::setUp(); @@ -53,16 +51,6 @@ protected function setUp(): void 'TRPS0200' ); - $this->order = [ - 'id' => 'order222', - 'ip' => '127.0.0.1', - 'amount' => '100.25', - 'installment' => 0, - 'currency' => PosInterface::CURRENCY_TRY, - 'success_url' => 'https://domain.com/success', - 'fail_url' => 'https://domain.com/fail_url', - 'lang' => PosInterface::LANG_TR, - ]; $this->dispatcher = $this->createMock(EventDispatcherInterface::class); $this->crypt = $this->createMock(CryptInterface::class); diff --git a/tests/Unit/DataMapper/ResponseDataMapper/EstPosResponseDataMapperTest.php b/tests/Unit/DataMapper/ResponseDataMapper/EstPosResponseDataMapperTest.php index ede214f3..f1c1945d 100644 --- a/tests/Unit/DataMapper/ResponseDataMapper/EstPosResponseDataMapperTest.php +++ b/tests/Unit/DataMapper/ResponseDataMapper/EstPosResponseDataMapperTest.php @@ -8,6 +8,7 @@ use Mews\Pos\Crypt\CryptInterface; use Mews\Pos\DataMapper\RequestDataMapper\EstPosRequestDataMapper; use Mews\Pos\DataMapper\ResponseDataMapper\EstPosResponseDataMapper; +use Mews\Pos\Exceptions\NotImplementedException; use Mews\Pos\PosInterface; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -197,7 +198,7 @@ public function testMapCancelResponse(array $responseData, array $expectedData): /** * @dataProvider orderHistoryTestDataProvider */ - public function testMapHistoryResponse(array $responseData, array $expectedData): void + public function testMapOrderHistoryResponse(array $responseData, array $expectedData): void { $actualData = $this->responseDataMapper->mapOrderHistoryResponse($responseData); if (count($responseData['Extra']) > 0) { @@ -228,6 +229,11 @@ public function testMapHistoryResponse(array $responseData, array $expectedData) $this->assertSame($expectedData, $actualData); } + public function testMapHistoryResponse(): void + { + $this->expectException(NotImplementedException::class); + $this->responseDataMapper->mapHistoryResponse([]); + } public static function paymentTestDataProvider(): iterable { From 42ef35037fafc4cbb3583f58986569549e10a223 Mon Sep 17 00:00:00 2001 From: mustapayev Date: Sat, 12 Oct 2024 13:17:10 +0200 Subject: [PATCH 5/7] minor changes --- .../ResponseDataMapper/AbstractResponseDataMapper.php | 6 +++--- src/Serializer/EstPosSerializer.php | 3 +-- src/Serializer/InterPosSerializer.php | 6 ++---- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/DataMapper/ResponseDataMapper/AbstractResponseDataMapper.php b/src/DataMapper/ResponseDataMapper/AbstractResponseDataMapper.php index ba6f8031..84c6c518 100644 --- a/src/DataMapper/ResponseDataMapper/AbstractResponseDataMapper.php +++ b/src/DataMapper/ResponseDataMapper/AbstractResponseDataMapper.php @@ -67,13 +67,13 @@ public function mapTxType($txType): ?string } /** - * @param string|int $txType + * @param string|int $securityType * * @return PosInterface::MODEL_*|null */ - public function mapSecurityType($txType): ?string + public function mapSecurityType($securityType): ?string { - return $this->secureTypeMappings[$txType] ?? null; + return $this->secureTypeMappings[$securityType] ?? null; } diff --git a/src/Serializer/EstPosSerializer.php b/src/Serializer/EstPosSerializer.php index e52b9c20..0b5d2eab 100644 --- a/src/Serializer/EstPosSerializer.php +++ b/src/Serializer/EstPosSerializer.php @@ -9,7 +9,6 @@ use Mews\Pos\Gateways\EstV3Pos; use Symfony\Component\Serializer\Encoder\XmlEncoder; use Symfony\Component\Serializer\Serializer; -use function in_array; class EstPosSerializer implements SerializerInterface { @@ -30,7 +29,7 @@ public function __construct() */ public static function supports(string $gatewayClass): bool { - return in_array($gatewayClass, [EstPos::class, EstV3Pos::class]); + return \in_array($gatewayClass, [EstPos::class, EstV3Pos::class]); } /** diff --git a/src/Serializer/InterPosSerializer.php b/src/Serializer/InterPosSerializer.php index 81f442ec..861f85c5 100644 --- a/src/Serializer/InterPosSerializer.php +++ b/src/Serializer/InterPosSerializer.php @@ -7,8 +7,6 @@ use Mews\Pos\Gateways\InterPos; use Symfony\Component\Serializer\Exception\NotEncodableValueException; -use function explode; -use function preg_split; class InterPosSerializer implements SerializerInterface { @@ -36,14 +34,14 @@ public function encode(array $data, ?string $txType = null): array public function decode(string $data, ?string $txType = null): array { //genelde ;; delimiter kullanilmis, ama bazen arasinda ;;; boyle delimiter de var. - $resultValues = preg_split('/(;;;|;;)/', $data); + $resultValues = \preg_split('/(;;;|;;)/', $data); if (false === $resultValues) { throw new NotEncodableValueException(); } $result = []; foreach ($resultValues as $val) { - [$key, $value] = explode('=', $val); + [$key, $value] = \explode('=', $val); $result[$key] = $value; } From cc8d3f81b206c0b8e0a6aade1467c2704e3eb65c Mon Sep 17 00:00:00 2001 From: mustapayev Date: Sat, 12 Oct 2024 13:18:18 +0200 Subject: [PATCH 6/7] KuveytPos - remove unsupported currencies --- .../RequestDataMapper/KuveytPosRequestDataMapper.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/DataMapper/RequestDataMapper/KuveytPosRequestDataMapper.php b/src/DataMapper/RequestDataMapper/KuveytPosRequestDataMapper.php index 12ba644e..62a52c2d 100644 --- a/src/DataMapper/RequestDataMapper/KuveytPosRequestDataMapper.php +++ b/src/DataMapper/RequestDataMapper/KuveytPosRequestDataMapper.php @@ -65,9 +65,6 @@ class KuveytPosRequestDataMapper extends AbstractRequestDataMapper PosInterface::CURRENCY_TRY => '0949', PosInterface::CURRENCY_USD => '0840', PosInterface::CURRENCY_EUR => '0978', - PosInterface::CURRENCY_GBP => '0826', - PosInterface::CURRENCY_JPY => '0392', - PosInterface::CURRENCY_RUB => '0810', ]; /** @var KuveytPosCrypt */ From 42955c4e54c0077891979d2189504ccd1167c6a7 Mon Sep 17 00:00:00 2001 From: mustapayev Date: Sat, 12 Oct 2024 13:24:57 +0200 Subject: [PATCH 7/7] interpos - remove ref_ret_num mapping as it is always equal to "hostid" --- .../InterPosResponseDataMapper.php | 8 ++--- .../InterPosResponseDataMapperTest.php | 30 +++++++++---------- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/src/DataMapper/ResponseDataMapper/InterPosResponseDataMapper.php b/src/DataMapper/ResponseDataMapper/InterPosResponseDataMapper.php index 41435167..a90d942a 100644 --- a/src/DataMapper/ResponseDataMapper/InterPosResponseDataMapper.php +++ b/src/DataMapper/ResponseDataMapper/InterPosResponseDataMapper.php @@ -48,7 +48,6 @@ public function mapPaymentResponse(array $rawPaymentResponseData, string $txType $result['order_id'] = $rawPaymentResponseData['OrderId']; $result['transaction_id'] = $rawPaymentResponseData['TransId']; $result['auth_code'] = $rawPaymentResponseData['AuthCode']; - $result['ref_ret_num'] = $rawPaymentResponseData['HostRefNum']; $result['error_code'] = $rawPaymentResponseData['ErrorCode']; $result['error_message'] = $rawPaymentResponseData['ErrorMessage']; $result['currency'] = $order['currency']; @@ -118,7 +117,7 @@ public function mapRefundResponse(array $rawResponseData): array 'order_id' => $rawResponseData['OrderId'], 'group_id' => null, 'auth_code' => null, - 'ref_ret_num' => $rawResponseData['HostRefNum'], + 'ref_ret_num' => null, 'proc_return_code' => $procReturnCode, 'transaction_id' => $rawResponseData['TransId'], 'error_code' => $rawResponseData['ErrorCode'], @@ -145,7 +144,7 @@ public function mapCancelResponse($rawResponseData): array 'order_id' => $rawResponseData['OrderId'], 'group_id' => null, 'auth_code' => $rawResponseData['AuthCode'], - 'ref_ret_num' => $rawResponseData['HostRefNum'], + 'ref_ret_num' => null, 'proc_return_code' => $procReturnCode, 'transaction_id' => $rawResponseData['TransId'], 'error_code' => $rawResponseData['ErrorCode'], @@ -180,7 +179,6 @@ public function mapStatusResponse(array $rawResponseData): array $defaultResponse['refund_amount'] = $rawResponseData['RefundedAmount'] > 0 ? $this->formatAmount($rawResponseData['RefundedAmount']) : null; // todo success cevap ornegi bulundugunda guncellenecek: - $defaultResponse['ref_ret_num'] = null; $defaultResponse['order_status'] = null; $defaultResponse['capture_amount'] = null; $defaultResponse['capture'] = null; @@ -309,7 +307,6 @@ private function map3DPaymentResponse(?array $rawPaymentResponseData, string $tx $result['order_id'] = $rawPaymentResponseData['OrderId']; $result['transaction_id'] = $rawPaymentResponseData['TransId']; $result['auth_code'] = $rawPaymentResponseData['AuthCode']; - $result['ref_ret_num'] = $rawPaymentResponseData['HostRefNum']; $result['error_code'] = $rawPaymentResponseData['ErrorCode']; $result['error_message'] = $rawPaymentResponseData['ErrorMessage']; $result['all'] = $rawPaymentResponseData; @@ -353,7 +350,6 @@ private function map3DCommonResponseData(array $raw3DAuthResponseData, ?array $r $threeDResponse = [ 'order_id' => $paymentResponseData['order_id'] ?? $raw3DAuthResponseData['OrderId'], 'proc_return_code' => $paymentResponseData['proc_return_code'] ?? $procReturnCode, - 'ref_ret_num' => $paymentResponseData['ref_ret_num'] ?? $raw3DAuthResponseData['HostRefNum'], 'transaction_security' => null === $mdStatus ? null : $this->mapResponseTransactionSecurity($mdStatus), 'payment_model' => $paymentModel, 'md_status' => $mdStatus, diff --git a/tests/Unit/DataMapper/ResponseDataMapper/InterPosResponseDataMapperTest.php b/tests/Unit/DataMapper/ResponseDataMapper/InterPosResponseDataMapperTest.php index 5516cf02..5eb608de 100644 --- a/tests/Unit/DataMapper/ResponseDataMapper/InterPosResponseDataMapperTest.php +++ b/tests/Unit/DataMapper/ResponseDataMapper/InterPosResponseDataMapperTest.php @@ -233,7 +233,7 @@ public static function paymentTestDataProvider(): array 'responseData' => [ 'OrderId' => '20221225662C', 'ProcReturnCode' => '81', - 'HostRefNum' => 'hostid', + 'HostRefNum' => null, 'AuthCode' => '', 'TxnResult' => 'Failed', 'ErrorMessage' => 'Terminal Aktif Degil', @@ -266,7 +266,7 @@ public static function paymentTestDataProvider(): array 'amount' => 1.01, 'payment_model' => 'regular', 'auth_code' => null, - 'ref_ret_num' => 'hostid', + 'ref_ret_num' => null, 'batch_num' => null, 'proc_return_code' => '81', 'status' => 'declined', @@ -340,7 +340,7 @@ public static function threeDPaymentDataProvider(): array 'FailUrl' => 'http:\/\/localhost\/interpos\/3d\/response.php', '3DStatus' => '0', 'AuthCode' => null, - 'HostRefNum' => 'hostid', + 'HostRefNum' => null, 'TransId' => null, 'TRXDATE' => null, 'CardHolderName' => null, @@ -359,7 +359,7 @@ public static function threeDPaymentDataProvider(): array 'order_id' => '20221225E1DF', 'transaction_id' => null, 'auth_code' => null, - 'ref_ret_num' => 'hostid', + 'ref_ret_num' => null, 'batch_num' => null, 'proc_return_code' => '81', 'status' => 'declined', @@ -413,7 +413,7 @@ public static function threeDPaymentDataProvider(): array 'FailUrl' => 'gizlendi', '3DStatus' => '1', 'AuthCode' => '', - 'HostRefNum' => 'hostid', + 'HostRefNum' => null, 'TransId' => '', 'TRXDATE' => '', 'CardHolderName' => '', @@ -430,7 +430,7 @@ public static function threeDPaymentDataProvider(): array 'paymentData' => [ 'OrderId' => '33554969', 'ProcReturnCode' => '00', - 'HostRefNum' => 'hostid', + 'HostRefNum' => null, 'AuthCode' => 'auth-code-123', 'TxnResult' => 'Success', 'ErrorMessage' => '', @@ -471,7 +471,7 @@ public static function threeDPaymentDataProvider(): array 'order_id' => '33554969', 'transaction_id' => 'trans-id-123', 'auth_code' => 'auth-code-123', - 'ref_ret_num' => 'hostid', + 'ref_ret_num' => null, 'batch_num' => null, 'proc_return_code' => '00', 'status' => 'approved', @@ -532,7 +532,7 @@ public static function threeDPayPaymentDataProvider(): array 'FailUrl' => 'http:\/\/localhost\/interpos\/3d-pay\/response.php', '3DStatus' => '0', 'AuthCode' => '', - 'HostRefNum' => 'hostid', + 'HostRefNum' => null, 'TransId' => '', 'TRXDATE' => '', 'CardHolderName' => '', @@ -550,7 +550,7 @@ public static function threeDPayPaymentDataProvider(): array 'order_id' => '20221225B83B', 'transaction_id' => null, 'auth_code' => null, - 'ref_ret_num' => 'hostid', + 'ref_ret_num' => null, 'batch_num' => null, 'proc_return_code' => '81', 'status' => 'declined', @@ -611,7 +611,7 @@ public static function threeDHostPaymentDataProvider(): array 'FailUrl' => 'http:\/\/localhost\/interpos\/3d-host\/response.php', '3DStatus' => '0', 'AuthCode' => '', - 'HostRefNum' => 'hostid', + 'HostRefNum' => null, 'TransId' => '', 'TRXDATE' => '', 'CardHolderName' => '', @@ -631,7 +631,7 @@ public static function threeDHostPaymentDataProvider(): array 'order_id' => '202212256D26', 'transaction_id' => null, 'auth_code' => null, - 'ref_ret_num' => 'hostid', + 'ref_ret_num' => null, 'batch_num' => null, 'proc_return_code' => '81', 'status' => 'declined', @@ -714,7 +714,7 @@ public static function cancelTestDataProvider(): array 'responseData' => [ 'OrderId' => 'SYSOID121330755', 'ProcReturnCode' => '81', - 'HostRefNum' => 'hostid', + 'HostRefNum' => null, 'AuthCode' => '', 'TxnResult' => 'Failed', 'ErrorMessage' => 'Terminal Aktif Degil', @@ -743,7 +743,7 @@ public static function cancelTestDataProvider(): array 'order_id' => 'SYSOID121330755', 'group_id' => null, 'auth_code' => null, - 'ref_ret_num' => 'hostid', + 'ref_ret_num' => null, 'proc_return_code' => '81', 'transaction_id' => null, 'error_code' => 'B810002', @@ -762,7 +762,7 @@ public static function refundTestDataProvider(): array 'responseData' => [ 'OrderId' => 'SYSOID121332551', 'ProcReturnCode' => '81', - 'HostRefNum' => 'hostid', + 'HostRefNum' => null, 'AuthCode' => '', 'TxnResult' => 'Failed', 'ErrorMessage' => 'Terminal Aktif Degil', @@ -791,7 +791,7 @@ public static function refundTestDataProvider(): array 'order_id' => 'SYSOID121332551', 'group_id' => null, 'auth_code' => null, - 'ref_ret_num' => 'hostid', + 'ref_ret_num' => null, 'proc_return_code' => '81', 'transaction_id' => null, 'error_code' => 'B810002',