diff --git a/craftgate/adapter/payment_adapter.py b/craftgate/adapter/payment_adapter.py index 8e73c91..e7c5c6b 100644 --- a/craftgate/adapter/payment_adapter.py +++ b/craftgate/adapter/payment_adapter.py @@ -16,6 +16,7 @@ from craftgate.request.init_apm_deposit_payment_request import InitApmDepositPaymentRequest from craftgate.request.init_apm_payment_request import InitApmPaymentRequest from craftgate.request.init_bnpl_payment_request import InitBnplPaymentRequest +from craftgate.request.init_checkout_card_verify_request import InitCheckoutCardVerifyRequest from craftgate.request.init_checkout_payment_request import InitCheckoutPaymentRequest from craftgate.request.init_garanti_pay_payment_request import InitGarantiPayPaymentRequest from craftgate.request.init_pos_apm_payment_request import InitPosApmPaymentRequest @@ -31,6 +32,7 @@ from craftgate.request.store_card_request import StoreCardRequest from craftgate.request.update_card_request import UpdateCardRequest from craftgate.request.update_payment_transaction_request import UpdatePaymentTransactionRequest +from craftgate.request.verify_card_request import VerifyCardRequest from craftgate.request_options import RequestOptions from craftgate.response.apm_deposit_payment_response import ApmDepositPaymentResponse from craftgate.response.apm_payment_complete_response import ApmPaymentCompleteResponse @@ -40,6 +42,7 @@ from craftgate.response.deposit_payment_response import DepositPaymentResponse from craftgate.response.fund_transfer_deposit_payment_response import FundTransferDepositPaymentResponse from craftgate.response.init_bnpl_payment_response import InitBnplPaymentResponse +from craftgate.response.init_checkout_card_verify_response import InitCheckoutCardVerifyResponse from craftgate.response.init_checkout_payment_response import InitCheckoutPaymentResponse from craftgate.response.init_garanti_pay_payment_response import InitGarantiPayPaymentResponse from craftgate.response.init_pos_apm_payment_response import InitPosApmPaymentResponse @@ -55,6 +58,7 @@ from craftgate.response.retrieve_loyalties_response import RetrieveLoyaltiesResponse from craftgate.response.stored_card_list_response import StoredCardListResponse from craftgate.response.stored_card_response import StoredCardResponse +from craftgate.response.verify_card_response import VerifyCardResponse from craftgate.utils.hash_generator import HashGenerator from craftgate.utils.request_query_params_builder import RequestQueryParamsBuilder @@ -130,6 +134,17 @@ def init_checkout_payment(self, request: InitCheckoutPaymentRequest) -> InitChec response_type=InitCheckoutPaymentResponse ) + def init_checkout_card_verify(self, request: InitCheckoutCardVerifyRequest) -> InitCheckoutCardVerifyResponse: + path = "/payment/v1/checkout-card-verify/init" + headers = self._create_headers(request, path) + return self._http_client.request( + method="POST", + url=self.request_options.base_url + path, + headers=headers, + body=request, + response_type=InitCheckoutCardVerifyResponse + ) + def retrieve_checkout_payment(self, token: str) -> PaymentResponse: path = "/payment/v1/checkout-payments/{}".format(token) headers = self._create_headers(None, path) @@ -413,6 +428,17 @@ def delete_stored_card(self, request: DeleteStoredCardRequest) -> None: response_type=None ) + def verify_card(self, request: VerifyCardRequest) -> VerifyCardResponse: + path = "/payment/v1/cards/verify" + headers = self._create_headers(request, path) + return self._http_client.request( + method="POST", + url=self.request_options.base_url + path, + headers=headers, + body=request, + response_type=VerifyCardResponse + ) + def approve_payment_transactions(self, request: ApprovePaymentTransactionsRequest) -> PaymentTransactionApprovalListResponse: path = "/payment/v1/payment-transactions/approve" diff --git a/craftgate/model/__init__.py b/craftgate/model/__init__.py index f2763c1..ccb5853 100644 --- a/craftgate/model/__init__.py +++ b/craftgate/model/__init__.py @@ -12,6 +12,8 @@ from .card_expiry_status import CardExpiryStatus from .card_provider import CardProvider from .card_type import CardType +from .card_verification_auth_type import CardVerificationAuthType +from .card_verify_status import CardVerifyStatus from .currency import Currency from .file_status import FileStatus from .fraud_action import FraudAction diff --git a/craftgate/model/card_verification_auth_type.py b/craftgate/model/card_verification_auth_type.py new file mode 100644 index 0000000..3a1a947 --- /dev/null +++ b/craftgate/model/card_verification_auth_type.py @@ -0,0 +1,7 @@ +from enum import Enum + + +class CardVerificationAuthType(str, Enum): + NON_THREE_DS = "NON_THREE_DS" + THREE_DS = "THREE_DS" + NONE = "NONE" diff --git a/craftgate/model/card_verify_status.py b/craftgate/model/card_verify_status.py new file mode 100644 index 0000000..ee955a7 --- /dev/null +++ b/craftgate/model/card_verify_status.py @@ -0,0 +1,7 @@ +from enum import Enum + + +class CardVerifyStatus(str, Enum): + SUCCESS = "SUCCESS" + FAILURE = "FAILURE" + THREE_DS_PENDING = "THREE_DS_PENDING" diff --git a/craftgate/request/__init__.py b/craftgate/request/__init__.py index a0df030..868ec19 100644 --- a/craftgate/request/__init__.py +++ b/craftgate/request/__init__.py @@ -30,6 +30,7 @@ from .init_apm_payment_request import InitApmPaymentRequest from .init_bkm_express_request import InitBkmExpressRequest from .init_bnpl_payment_request import InitBnplPaymentRequest +from .init_checkout_card_verify_request import InitCheckoutCardVerifyRequest from .init_checkout_payment_request import InitCheckoutPaymentRequest from .init_garanti_pay_payment_request import InitGarantiPayPaymentRequest from .init_juzdan_payment_request import InitJuzdanPaymentRequest @@ -77,3 +78,4 @@ from .update_payout_account_request import UpdatePayoutAccountRequest from .update_product_request import UpdateProductRequest from .update_wallet_request import UpdateWalletRequest +from .verify_card_request import VerifyCardRequest diff --git a/craftgate/request/dto/__init__.py b/craftgate/request/dto/__init__.py index b983445..bb19442 100644 --- a/craftgate/request/dto/__init__.py +++ b/craftgate/request/dto/__init__.py @@ -11,3 +11,4 @@ from .tokenized_card import TokenizedCard from .update_merchant_pos_commission import UpdateMerchantPosCommission from .update_merchant_pos_user import UpdateMerchantPosUser +from .verify_card import VerifyCard diff --git a/craftgate/request/dto/verify_card.py b/craftgate/request/dto/verify_card.py new file mode 100644 index 0000000..cd1dae5 --- /dev/null +++ b/craftgate/request/dto/verify_card.py @@ -0,0 +1,21 @@ +from typing import Optional + + +class VerifyCard(object): + def __init__( + self, + card_holder_name: Optional[str] = None, + card_number: Optional[str] = None, + expire_year: Optional[str] = None, + expire_month: Optional[str] = None, + cvc: Optional[str] = None, + card_alias: Optional[str] = None, + card_user_key: Optional[str] = None + ) -> None: + self.card_holder_name = card_holder_name + self.card_number = card_number + self.expire_year = expire_year + self.expire_month = expire_month + self.cvc = cvc + self.card_alias = card_alias + self.card_user_key = card_user_key diff --git a/craftgate/request/init_checkout_card_verify_request.py b/craftgate/request/init_checkout_card_verify_request.py new file mode 100644 index 0000000..42085d9 --- /dev/null +++ b/craftgate/request/init_checkout_card_verify_request.py @@ -0,0 +1,25 @@ +from decimal import Decimal +from typing import Optional + +from craftgate.model.card_verification_auth_type import CardVerificationAuthType +from craftgate.model.currency import Currency + + +class InitCheckoutCardVerifyRequest(object): + def __init__( + self, + verification_price: Optional[Decimal] = None, + currency: Optional[Currency] = None, + conversation_id: Optional[str] = None, + callback_url: Optional[str] = None, + card_user_key: Optional[str] = None, + payment_authentication_type: Optional[CardVerificationAuthType] = None, + ttl: Optional[int] = None + ) -> None: + self.verification_price = verification_price + self.currency = currency + self.conversation_id = conversation_id + self.callback_url = callback_url + self.card_user_key = card_user_key + self.payment_authentication_type = payment_authentication_type + self.ttl = ttl diff --git a/craftgate/request/verify_card_request.py b/craftgate/request/verify_card_request.py new file mode 100644 index 0000000..001f967 --- /dev/null +++ b/craftgate/request/verify_card_request.py @@ -0,0 +1,26 @@ +from decimal import Decimal +from typing import Optional + +from craftgate.model.card_verification_auth_type import CardVerificationAuthType +from craftgate.model.currency import Currency +from craftgate.request.dto.verify_card import VerifyCard + + +class VerifyCardRequest(object): + def __init__( + self, + card: Optional[VerifyCard] = None, + payment_authentication_type: Optional[CardVerificationAuthType] = None, + verification_price: Optional[Decimal] = None, + currency: Optional[Currency] = None, + client_ip: Optional[str] = None, + conversation_id: Optional[str] = None, + callback_url: Optional[str] = None + ) -> None: + self.card = card + self.payment_authentication_type = payment_authentication_type + self.verification_price = verification_price + self.currency = currency + self.client_ip = client_ip + self.conversation_id = conversation_id + self.callback_url = callback_url diff --git a/craftgate/response/__init__.py b/craftgate/response/__init__.py index 54aafa6..d99aa15 100644 --- a/craftgate/response/__init__.py +++ b/craftgate/response/__init__.py @@ -17,6 +17,7 @@ from .fund_transfer_deposit_payment_response import FundTransferDepositPaymentResponse from .init_bkm_express_response import InitBkmExpressResponse from .init_bnpl_payment_response import InitBnplPaymentResponse +from .init_checkout_card_verify_response import InitCheckoutCardVerifyResponse from .init_checkout_payment_response import InitCheckoutPaymentResponse from .init_garanti_pay_payment_response import InitGarantiPayPaymentResponse from .init_juzdan_payment_response import InitJuzdanPaymentResponse @@ -70,5 +71,6 @@ from .wallet_transaction_list_response import WalletTransactionListResponse from .wallet_transaction_refundable_amount_response import WalletTransactionRefundableAmountResponse from .wallet_transaction_response import WalletTransactionResponse +from .verify_card_response import VerifyCardResponse from .withdraw_list_response import WithdrawListResponse from .withdraw_response import WithdrawResponse diff --git a/craftgate/response/init_checkout_card_verify_response.py b/craftgate/response/init_checkout_card_verify_response.py new file mode 100644 index 0000000..456c748 --- /dev/null +++ b/craftgate/response/init_checkout_card_verify_response.py @@ -0,0 +1,14 @@ +from datetime import datetime +from typing import Optional + + +class InitCheckoutCardVerifyResponse(object): + def __init__( + self, + token: Optional[str] = None, + page_url: Optional[str] = None, + token_expire_date: Optional[datetime] = None + ) -> None: + self.token = token + self.page_url = page_url + self.token_expire_date = token_expire_date diff --git a/craftgate/response/verify_card_response.py b/craftgate/response/verify_card_response.py new file mode 100644 index 0000000..0a4d68b --- /dev/null +++ b/craftgate/response/verify_card_response.py @@ -0,0 +1,24 @@ +from typing import Optional + +from craftgate.model.card_verify_status import CardVerifyStatus +from craftgate.model.refund_status import RefundStatus + + +class VerifyCardResponse(object): + def __init__( + self, + card_user_key: Optional[str] = None, + card_token: Optional[str] = None, + html_content: Optional[str] = None, + redirect_url: Optional[str] = None, + merchant_callback_url: Optional[str] = None, + refund_status: Optional[RefundStatus] = None, + card_verify_status: Optional[CardVerifyStatus] = None + ) -> None: + self.card_user_key = card_user_key + self.card_token = card_token + self.html_content = html_content + self.redirect_url = redirect_url + self.merchant_callback_url = merchant_callback_url + self.refund_status = refund_status + self.card_verify_status = card_verify_status diff --git a/tests/test_payment_sample.py b/tests/test_payment_sample.py index d8414e6..0421dc4 100644 --- a/tests/test_payment_sample.py +++ b/tests/test_payment_sample.py @@ -5,18 +5,20 @@ from decimal import Decimal from craftgate import Craftgate, RequestOptions, PaymentTransaction, FraudCheckParameters -from craftgate.model import AdditionalAction, ApmAdditionalAction, ApmType, CardAssociation, CardProvider, CardType, \ - Currency, Loyalty, LoyaltyParams, LoyaltyType, PaymentGroup, PaymentPhase, PaymentStatus, PaymentType, \ +from craftgate.model import AdditionalAction, ApmAdditionalAction, ApmType, CardAssociation, CardProvider, \ + CardType, CardVerificationAuthType, CardVerifyStatus, Currency, Loyalty, LoyaltyParams, LoyaltyType, \ + PaymentGroup, PaymentPhase, PaymentStatus, PaymentType, \ PosApmPaymentProvider, RefundDestinationType, RefundStatus, Reward, WalletTransactionType from craftgate.request import ApprovePaymentTransactionsRequest, CloneCardRequest, CompleteApmPaymentRequest, \ CompletePosApmPaymentRequest, CompleteThreeDSPaymentRequest, CreateApmPaymentRequest, CreateDepositPaymentRequest, \ CreateFundTransferDepositPaymentRequest, CreatePaymentRequest, DeleteStoredCardRequest, \ - DisapprovePaymentTransactionsRequest, Card, GarantiPayInstallment, PaymentItem, InitApmDepositPaymentRequest, \ - InitApmPaymentRequest, InitCheckoutPaymentRequest, InitGarantiPayPaymentRequest, InitPosApmPaymentRequest, \ - InitThreeDSPaymentRequest, PostAuthPaymentRequest, RefundPaymentRequest, \ + DisapprovePaymentTransactionsRequest, Card, GarantiPayInstallment, InitApmDepositPaymentRequest, \ + InitApmPaymentRequest, InitCheckoutCardVerifyRequest, InitCheckoutPaymentRequest, \ + InitGarantiPayPaymentRequest, InitPosApmPaymentRequest, InitThreeDSPaymentRequest, PaymentItem, \ + PostAuthPaymentRequest, RefundPaymentRequest, \ RefundPaymentTransactionMarkAsRefundedRequest, RefundPaymentTransactionRequest, RetrieveLoyaltiesRequest, \ RetrieveProviderCardRequest, SearchStoredCardsRequest, StoreCardRequest, UpdateCardRequest, \ - UpdatePaymentTransactionRequest + UpdatePaymentTransactionRequest, VerifyCard, VerifyCardRequest from craftgate.response import MultiPaymentResponse, PaymentTransactionApprovalListResponse, PaymentTransactionResponse, \ StoredCardListResponse @@ -1647,6 +1649,45 @@ def test_should_not_validate_3d_secure_callback_when_hashes_are_not_equal(self): is_verified = self.payment.is_3d_secure_callback_verified(merchant_key, params) self.assertFalse(is_verified) + def test_init_checkout_card_verify_with_non_3ds_auth_type(self): + req = InitCheckoutCardVerifyRequest() + req.callback_url = "https://www.your-website.com/craftgate-checkout-card-verify-callback" + req.conversation_id = "456d1297-908e-4bd6-a13b-4be31a6e47d5" + req.payment_authentication_type = CardVerificationAuthType.NON_THREE_DS + req.verification_price = Decimal("10") + req.currency = Currency.TRY + + resp = self.payment.init_checkout_card_verify(req) + print(resp) + self.assertIsNotNone(resp) + self.assertIsNotNone(getattr(resp, "page_url", None)) + self.assertIsNotNone(getattr(resp, "token", None)) + self.assertIsNotNone(getattr(resp, "token_expire_date", None)) + + def test_verify_card_with_3ds(self): + card = VerifyCard() + card.card_holder_name = "Haluk Demir" + card.card_number = "5258640000000001" + card.expire_year = "2044" + card.expire_month = "07" + card.cvc = "000" + card.card_alias = "My YKB Card" + + req = VerifyCardRequest() + req.card = card + req.payment_authentication_type = CardVerificationAuthType.THREE_DS + req.callback_url = "https://www.your-website.com/craftgate-3DSecure-card-verify-callback" + req.conversation_id = "456d1297-908e-4bd6-a13b-4be31a6e47d5" + req.verification_price = Decimal("10") + req.currency = Currency.TRY + req.client_ip = "127.0.0.1" + + resp = self.payment.verify_card(req) + print(resp) + self.assertIsNotNone(resp) + self.assertEqual(CardVerifyStatus.THREE_DS_PENDING, resp.card_verify_status) + self.assertIsNotNone(getattr(resp, "html_content", None)) + if __name__ == "__main__": unittest.main()