dana-python 2.1.0__tar.gz → 2.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dana_python-2.1.0/dana_python.egg-info → dana_python-2.1.1}/PKG-INFO +4 -4
- {dana_python-2.1.0 → dana_python-2.1.1}/README.md +1 -1
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/exceptions.py +5 -1
- dana_python-2.1.1/dana/payment_gateway/v1/custom_validation.py +333 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/utils/script.py +16 -1
- dana_python-2.1.1/dana/widget/v1/custom_validation.py +112 -0
- {dana_python-2.1.0 → dana_python-2.1.1/dana_python.egg-info}/PKG-INFO +4 -4
- {dana_python-2.1.0 → dana_python-2.1.1}/dana_python.egg-info/requires.txt +2 -2
- {dana_python-2.1.0 → dana_python-2.1.1}/pyproject.toml +4 -4
- {dana_python-2.1.0 → dana_python-2.1.1}/tests/test_payment_gateway_api.py +132 -0
- dana_python-2.1.0/dana/payment_gateway/v1/custom_validation.py +0 -262
- dana_python-2.1.0/dana/widget/v1/custom_validation.py +0 -80
- {dana_python-2.1.0 → dana_python-2.1.1}/LICENSE +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/__init__.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/api_client.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/api_response.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/base/__init__.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/base/configuration.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/base/model.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/base/types.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/disbursement/__init__.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/disbursement/v1/__init__.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/disbursement/v1/api/__init__.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/disbursement/v1/api/disbursement_api.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/disbursement/v1/enum.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/disbursement/v1/models/__init__.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/disbursement/v1/models/bank_account_inquiry_request.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/disbursement/v1/models/bank_account_inquiry_request_additional_info.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/disbursement/v1/models/bank_account_inquiry_response.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/disbursement/v1/models/bank_account_inquiry_response_additional_info.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/disbursement/v1/models/dana_account_inquiry_request.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/disbursement/v1/models/dana_account_inquiry_request_additional_info.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/disbursement/v1/models/dana_account_inquiry_response.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/disbursement/v1/models/money.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/disbursement/v1/models/transfer_to_bank_inquiry_status_request.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/disbursement/v1/models/transfer_to_bank_inquiry_status_response.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/disbursement/v1/models/transfer_to_bank_request.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/disbursement/v1/models/transfer_to_bank_request_additional_info.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/disbursement/v1/models/transfer_to_bank_response.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/disbursement/v1/models/transfer_to_dana_inquiry_status_request.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/disbursement/v1/models/transfer_to_dana_inquiry_status_response.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/disbursement/v1/models/transfer_to_dana_request.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/disbursement/v1/models/transfer_to_dana_request_additional_info.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/disbursement/v1/models/transfer_to_dana_response.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/__init__.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/__init__.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/api/__init__.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/api/merchant_management_api.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/enum.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/__init__.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/address_info.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/business_docs.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/create_division_request.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/create_division_request_ext_info.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/create_division_response.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/create_division_response_response.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/create_division_response_response_body.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/create_division_response_response_head.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/create_shop_request.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/create_shop_response.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/create_shop_response_response.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/create_shop_response_response_body.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/create_shop_response_response_head.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/division_resource_info.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/merchant_resource_information.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/mobile_no_info.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/pic_info.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/query_division_request.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/query_division_response.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/query_division_response_response.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/query_division_response_response_body.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/query_division_response_response_head.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/query_merchant_resource_request.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/query_merchant_resource_response.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/query_merchant_resource_response_response.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/query_merchant_resource_response_response_body.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/query_merchant_resource_response_response_head.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/query_shop_request.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/query_shop_response.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/query_shop_response_response.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/query_shop_response_response_body.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/query_shop_response_response_head.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/result_info.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/shop_resource_info.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/update_division_request.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/update_division_response.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/update_division_response_response.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/update_division_response_response_body.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/update_division_response_response_head.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/update_shop_request.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/update_shop_response.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/update_shop_response_response.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/update_shop_response_response_head.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/user_name.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/__init__.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/__init__.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/api/__init__.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/api/payment_gateway_api.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/enum.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/__init__.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/actor_context.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/amount_detail.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/audit_info.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/buyer.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/cancel_order_request.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/cancel_order_response.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/consult_pay_payment_info.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/consult_pay_request.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/consult_pay_request_additional_info.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/consult_pay_response.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/create_order_by_api_additional_info.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/create_order_by_api_request.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/create_order_by_redirect_additional_info.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/create_order_by_redirect_request.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/create_order_response.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/create_order_response_additional_info.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/env_info.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/goods.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/money.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/order_api_object.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/order_redirect_object.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/pay_option_additional_info.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/pay_option_detail.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/pay_option_info.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/payment_view.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/promo_info.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/query_payment_request.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/query_payment_response.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/query_payment_response_additional_info.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/refund_option_bill.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/refund_order_request.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/refund_order_request_additional_info.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/refund_order_response.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/seller.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/shipping_info.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/status_detail.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/time_detail.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/url_param.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/virtual_account_info.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/rest.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/utils/date_validation.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/utils/models.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/utils/open_api_configuration.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/utils/open_api_header.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/utils/snap_configuration.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/utils/snap_header.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/utils/url.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/webhook/__init__.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/webhook/finish_notify_payment_info.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/webhook/finish_notify_request.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/webhook/finish_notify_request_additional_info.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/webhook/finish_notify_response.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/webhook/money.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/webhook/pay_option_info.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/webhook/shop_info.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/webhook/webhook.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/__init__.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/__init__.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/api/__init__.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/api/widget_api.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/enum.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/__init__.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/account_info.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/account_unbinding_request.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/account_unbinding_request_additional_info.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/account_unbinding_response.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/amount_detail.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/apply_ott_request.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/apply_ott_request_additional_info.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/apply_ott_response.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/apply_ott_response_user_resources_inner.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/apply_token_authorization_code_request.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/apply_token_refresh_token_request.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/apply_token_response.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/apply_token_response_additional_info.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/apply_token_response_additional_info_user_info.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/balance_inquiry_request.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/balance_inquiry_request_additional_info.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/balance_inquiry_response.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/buyer.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/cancel_order_request.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/cancel_order_response.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/env_info.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/goods.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/international_order_info.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/international_order_info_exchange_rate.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/money.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/oauth2_url_data.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/oauth2_url_data_seamless_data.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/order.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/pay_option_detail.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/pay_option_detail_additional_info.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/pay_option_info.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/payment_promo_info.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/payment_view.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/query_payment_request.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/query_payment_response.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/query_payment_response_additional_info.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/query_user_profile_request.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/query_user_profile_response.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/query_user_profile_response_response.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/query_user_profile_response_response_body.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/query_user_profile_response_response_head.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/refund_order_request.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/refund_order_request_additional_info.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/refund_order_response.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/refund_promo_info.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/result_info.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/seller.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/service_info.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/shipping_info.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/status_detail.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/time_detail.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/url_param.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/user_resource_info.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/virtual_account_info.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/widget_payment_request.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/widget_payment_request_additional_info.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/widget_payment_response.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/util.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana_python.egg-info/SOURCES.txt +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana_python.egg-info/dependency_links.txt +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/dana_python.egg-info/top_level.txt +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/setup.cfg +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/tests/test_disbursement_api.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/tests/test_merchant_management_api.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/tests/test_payment_gateway_with_automation.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/tests/test_snap_header.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/tests/test_webhook.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/tests/test_widget_api.py +0 -0
- {dana_python-2.1.0 → dana_python-2.1.1}/tests/test_widget_with_automation.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dana-python
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.1
|
|
4
4
|
Summary: API Client (SDK) for DANA APIs based on https://dashboard.dana.id/api-docs
|
|
5
5
|
Author-email: DANA Package Manager <package-manager@dana.id>
|
|
6
6
|
Maintainer-email: DANA Package Manager <package-manager@dana.id>
|
|
@@ -12,9 +12,9 @@ Requires-Python: >3.9.1
|
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
License-File: LICENSE
|
|
14
14
|
Requires-Dist: annotated-types==0.7.0
|
|
15
|
-
Requires-Dist: cffi==
|
|
15
|
+
Requires-Dist: cffi==2.0.0
|
|
16
16
|
Requires-Dist: cryptography<46.0.0,>=44.0.2
|
|
17
|
-
Requires-Dist: pycparser==2.
|
|
17
|
+
Requires-Dist: pycparser==2.23
|
|
18
18
|
Requires-Dist: pydantic<3.0.0,>=2.10.6
|
|
19
19
|
Requires-Dist: pydantic-core<3.0.0,>=2.27.2
|
|
20
20
|
Requires-Dist: python-dateutil==2.9.0.post0
|
|
@@ -88,7 +88,7 @@ Before using the SDK, please make sure to set the following environment variable
|
|
|
88
88
|
| ---------------------- | --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
|
|
89
89
|
| `ENV` or `DANA_ENV` | Defines which environment the SDK will use. Possible values: `SANDBOX` or `PRODUCTION`. | `SANDBOX` |
|
|
90
90
|
| `X_PARTNER_ID` | Unique identifier for partner, provided by DANA, also known as `clientId`. | 1970010100000000000000 |
|
|
91
|
-
| `PRIVATE_KEY` | Your private key string. |
|
|
91
|
+
| `PRIVATE_KEY` | Your private key string. | |
|
|
92
92
|
| `PRIVATE_KEY_PATH` | Path to your private key file. If both are set, `PRIVATE_KEY_PATH` is used. | /path/to/your_private_key.pem |
|
|
93
93
|
| `DANA_PUBLIC_KEY` | DANA public key string for parsing webhook. | `-----BEGIN PUBLIC KEY-----MIIBIjANBgkq...Do/QIDAQAB-----END PUBLIC KEY-----` |
|
|
94
94
|
| `DANA_PUBLIC_KEY_PATH` | Path to DANA public key file for parsing webhook. If both set, `DANA_PUBLIC_KEY_PATH is used. | /path/to/dana_public_key.pem |
|
|
@@ -63,7 +63,7 @@ Before using the SDK, please make sure to set the following environment variable
|
|
|
63
63
|
| ---------------------- | --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
|
|
64
64
|
| `ENV` or `DANA_ENV` | Defines which environment the SDK will use. Possible values: `SANDBOX` or `PRODUCTION`. | `SANDBOX` |
|
|
65
65
|
| `X_PARTNER_ID` | Unique identifier for partner, provided by DANA, also known as `clientId`. | 1970010100000000000000 |
|
|
66
|
-
| `PRIVATE_KEY` | Your private key string. |
|
|
66
|
+
| `PRIVATE_KEY` | Your private key string. | |
|
|
67
67
|
| `PRIVATE_KEY_PATH` | Path to your private key file. If both are set, `PRIVATE_KEY_PATH` is used. | /path/to/your_private_key.pem |
|
|
68
68
|
| `DANA_PUBLIC_KEY` | DANA public key string for parsing webhook. | `-----BEGIN PUBLIC KEY-----MIIBIjANBgkq...Do/QIDAQAB-----END PUBLIC KEY-----` |
|
|
69
69
|
| `DANA_PUBLIC_KEY_PATH` | Path to DANA public key file for parsing webhook. If both set, `DANA_PUBLIC_KEY_PATH is used. | /path/to/dana_public_key.pem |
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
Do not edit the class manually.
|
|
23
23
|
""" # noqa: E501
|
|
24
24
|
|
|
25
|
-
from typing import Any, Optional
|
|
25
|
+
from typing import Any, Dict, List, Optional
|
|
26
26
|
from typing_extensions import Self
|
|
27
27
|
|
|
28
28
|
class OpenApiException(Exception):
|
|
@@ -122,7 +122,11 @@ class ApiException(OpenApiException):
|
|
|
122
122
|
*,
|
|
123
123
|
body: Optional[str] = None,
|
|
124
124
|
data: Optional[Any] = None,
|
|
125
|
+
contexts: Optional[List[Dict[str, str]]] = None,
|
|
125
126
|
) -> None:
|
|
127
|
+
self.contexts = contexts
|
|
128
|
+
if contexts is not None and reason is None:
|
|
129
|
+
reason = "; ".join(f"{c['field']}: {c['message']}" for c in contexts)
|
|
126
130
|
self.status = status
|
|
127
131
|
self.reason = reason
|
|
128
132
|
self.body = body
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
# Copyright 2026 PT Espay Debit Indonesia Koe
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""
|
|
16
|
+
CustomValidation
|
|
17
|
+
|
|
18
|
+
This module provides custom validation functions for Payment Gateway API requests.
|
|
19
|
+
Validations are registered in the validation_registry and executed via custom_validation().
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import os
|
|
23
|
+
import re
|
|
24
|
+
from typing import Any, Callable, Dict, FrozenSet, List, Set
|
|
25
|
+
|
|
26
|
+
from dana.utils.date_validation import validate_valid_up_to_date
|
|
27
|
+
from dana.exceptions import ApiException
|
|
28
|
+
|
|
29
|
+
# Money value pattern: digits (1-16) + "." + exactly 2 digits (e.g. 10000.00)
|
|
30
|
+
MONEY_VALUE_PATTERN = re.compile(r'^\d{1,16}\.\d{2}$')
|
|
31
|
+
|
|
32
|
+
SANDBOX_ALLOWED_PAY_METHODS: FrozenSet[str] = frozenset({
|
|
33
|
+
'BALANCE', 'CREDIT_CARD', 'DEBIT_CARD', 'VIRTUAL_ACCOUNT', 'NETWORK_PAY',
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
SANDBOX_ALLOWED_PAY_OPTIONS: FrozenSet[str] = frozenset({
|
|
37
|
+
'CARD', 'QRIS', 'BRI', 'PANIN', 'CIMB', 'MANDIRI', 'BTPN',
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
CREDIT_DEBIT_CARD_PAY_METHODS: Set[str] = {'CREDIT_CARD', 'DEBIT_CARD'}
|
|
41
|
+
NETWORK_PAY_PG_CARD = 'NETWORK_PAY_PG_CARD'
|
|
42
|
+
EWALLET_PAY_OPTIONS: Set[str] = {
|
|
43
|
+
'NETWORK_PAY_PG_SPAY',
|
|
44
|
+
'NETWORK_PAY_PG_OVO',
|
|
45
|
+
'NETWORK_PAY_PG_GOPAY',
|
|
46
|
+
'NETWORK_PAY_PG_LINKAJA',
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _is_sandbox() -> bool:
|
|
51
|
+
env = os.getenv('DANA_ENV', os.getenv('ENV', 'sandbox')).lower()
|
|
52
|
+
return env == 'sandbox'
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _pay_option_allowed_in_sandbox(value: str) -> bool:
|
|
56
|
+
if not value or not str(value).strip():
|
|
57
|
+
return False
|
|
58
|
+
s = str(value).strip()
|
|
59
|
+
if s in SANDBOX_ALLOWED_PAY_OPTIONS:
|
|
60
|
+
return True
|
|
61
|
+
for opt in SANDBOX_ALLOWED_PAY_OPTIONS:
|
|
62
|
+
if s.endswith('_' + opt):
|
|
63
|
+
return True
|
|
64
|
+
return False
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _normalize_value(value: Any) -> str:
|
|
68
|
+
if value is None:
|
|
69
|
+
return ''
|
|
70
|
+
if hasattr(value, 'value'):
|
|
71
|
+
return str(getattr(value, 'value')).strip()
|
|
72
|
+
return str(value).strip()
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _trim_str(value: Any) -> str:
|
|
76
|
+
if value is None:
|
|
77
|
+
return ''
|
|
78
|
+
return str(value).strip()
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _rune_len(s: str) -> int:
|
|
82
|
+
return len(list(s))
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _ctx(field: str, message: str) -> Dict[str, str]:
|
|
86
|
+
return {'field': field, 'message': message}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def validate_additional_info_required(request: Any) -> None:
|
|
90
|
+
if request is None:
|
|
91
|
+
return
|
|
92
|
+
if hasattr(request, 'additional_info') and request.additional_info is None:
|
|
93
|
+
raise ApiException(status=0, contexts=[_ctx('additionalInfo', 'additionalInfo is required')])
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def validate_money_value_pattern(request: Any) -> None:
|
|
97
|
+
if request is None:
|
|
98
|
+
return
|
|
99
|
+
if not hasattr(request, 'amount') or request.amount is None:
|
|
100
|
+
return
|
|
101
|
+
value = getattr(request.amount, 'value', None)
|
|
102
|
+
if value is None or value == '':
|
|
103
|
+
raise ApiException(status=0, contexts=[_ctx('amount.value', 'amount.value is required')])
|
|
104
|
+
if not MONEY_VALUE_PATTERN.match(str(value)):
|
|
105
|
+
raise ApiException(status=0, contexts=[
|
|
106
|
+
_ctx('amount.value', f'amount.value must match pattern (e.g. 10000.00): got {value!r}')
|
|
107
|
+
])
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def validate_valid_up_to_create_order_request(request: Any) -> None:
|
|
111
|
+
if request is None:
|
|
112
|
+
return
|
|
113
|
+
if hasattr(request, 'valid_up_to') and request.valid_up_to is not None:
|
|
114
|
+
try:
|
|
115
|
+
validate_valid_up_to_date(request.valid_up_to)
|
|
116
|
+
except ValueError as e:
|
|
117
|
+
raise ApiException(status=0, contexts=[
|
|
118
|
+
_ctx('validUpTo', f'validUpTo validation failed: {str(e)}')
|
|
119
|
+
]) from e
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def validate_external_store_id_for_qris(request: Any) -> None:
|
|
123
|
+
if request is None:
|
|
124
|
+
return
|
|
125
|
+
if not hasattr(request, 'pay_option_details') or request.pay_option_details is None:
|
|
126
|
+
return
|
|
127
|
+
has_qris = False
|
|
128
|
+
if isinstance(request.pay_option_details, list):
|
|
129
|
+
for pay_option_detail in request.pay_option_details:
|
|
130
|
+
if hasattr(pay_option_detail, 'pay_option') and pay_option_detail.pay_option == 'NETWORK_PAY_PG_QRIS':
|
|
131
|
+
has_qris = True
|
|
132
|
+
break
|
|
133
|
+
if has_qris:
|
|
134
|
+
external_store_id = None
|
|
135
|
+
if hasattr(request, 'external_store_id'):
|
|
136
|
+
external_store_id = request.external_store_id
|
|
137
|
+
if not external_store_id or (isinstance(external_store_id, str) and external_store_id.strip() == ''):
|
|
138
|
+
raise ApiException(status=0, contexts=[
|
|
139
|
+
_ctx('externalStoreId', 'externalStoreId is required when payOption is NETWORK_PAY_PG_QRIS')
|
|
140
|
+
])
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def validate_sandbox_pay_method_and_pay_option(request: Any) -> None:
|
|
144
|
+
if request is None or not _is_sandbox():
|
|
145
|
+
return
|
|
146
|
+
pay_option_details = getattr(request, 'pay_option_details', None)
|
|
147
|
+
if not pay_option_details or not isinstance(pay_option_details, list):
|
|
148
|
+
return
|
|
149
|
+
for i, detail in enumerate(pay_option_details):
|
|
150
|
+
if not detail:
|
|
151
|
+
continue
|
|
152
|
+
if hasattr(detail, 'pay_method') and detail.pay_method is not None:
|
|
153
|
+
pm_str = _normalize_value(detail.pay_method)
|
|
154
|
+
if pm_str and pm_str not in SANDBOX_ALLOWED_PAY_METHODS:
|
|
155
|
+
raise ApiException(status=0, contexts=[
|
|
156
|
+
_ctx(
|
|
157
|
+
f'payOptionDetails[{i}].payMethod',
|
|
158
|
+
(
|
|
159
|
+
f'In sandbox, payMethod must be one of [{", ".join(sorted(SANDBOX_ALLOWED_PAY_METHODS))}]; '
|
|
160
|
+
f'got {pm_str}'
|
|
161
|
+
),
|
|
162
|
+
)
|
|
163
|
+
])
|
|
164
|
+
if hasattr(detail, 'pay_option') and detail.pay_option is not None:
|
|
165
|
+
po_str = _normalize_value(detail.pay_option)
|
|
166
|
+
if po_str and not _pay_option_allowed_in_sandbox(po_str):
|
|
167
|
+
raise ApiException(status=0, contexts=[
|
|
168
|
+
_ctx(
|
|
169
|
+
f'payOptionDetails[{i}].payOption',
|
|
170
|
+
(
|
|
171
|
+
f'In sandbox, payOption must be one of [{", ".join(sorted(SANDBOX_ALLOWED_PAY_OPTIONS))}] '
|
|
172
|
+
f'(or suffix like VIRTUAL_ACCOUNT_BRI); got {po_str}'
|
|
173
|
+
),
|
|
174
|
+
)
|
|
175
|
+
])
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def validate_conditional_pay_option_additional_info_create_order_request(request: Any) -> None:
|
|
179
|
+
if request is None:
|
|
180
|
+
return
|
|
181
|
+
pay_option_details = getattr(request, 'pay_option_details', None)
|
|
182
|
+
if not pay_option_details or not isinstance(pay_option_details, list):
|
|
183
|
+
return
|
|
184
|
+
|
|
185
|
+
contexts: List[Dict[str, str]] = []
|
|
186
|
+
|
|
187
|
+
for i, detail in enumerate(pay_option_details):
|
|
188
|
+
if not detail:
|
|
189
|
+
continue
|
|
190
|
+
pay_method = _trim_str(getattr(detail, 'pay_method', None))
|
|
191
|
+
pay_option = _trim_str(getattr(detail, 'pay_option', None))
|
|
192
|
+
additional_info = getattr(detail, 'additional_info', None)
|
|
193
|
+
phone_raw = None
|
|
194
|
+
if additional_info is not None:
|
|
195
|
+
phone_raw = getattr(additional_info, 'phone_number', None)
|
|
196
|
+
phone_number = _trim_str(phone_raw)
|
|
197
|
+
|
|
198
|
+
is_card = pay_method in CREDIT_DEBIT_CARD_PAY_METHODS or pay_option == NETWORK_PAY_PG_CARD
|
|
199
|
+
is_ewallet = pay_option in EWALLET_PAY_OPTIONS
|
|
200
|
+
|
|
201
|
+
if is_card or is_ewallet:
|
|
202
|
+
field = f'payOptionDetails[{i}].additionalInfo.phoneNumber'
|
|
203
|
+
if not phone_number:
|
|
204
|
+
contexts.append(
|
|
205
|
+
_ctx(field, f'phoneNumber is required for card/e-wallet payment (payOptionDetails[{i}])')
|
|
206
|
+
)
|
|
207
|
+
else:
|
|
208
|
+
ln = _rune_len(phone_number)
|
|
209
|
+
if ln < 1 or ln > 15:
|
|
210
|
+
contexts.append(
|
|
211
|
+
_ctx(field, f'phoneNumber must be between 1 and 15 characters (payOptionDetails[{i}])')
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
if contexts:
|
|
215
|
+
raise ApiException(status=0, contexts=contexts)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def validate_optional_fields_with_required_nested_create_order_request(request: Any) -> None:
|
|
219
|
+
if request is None:
|
|
220
|
+
return
|
|
221
|
+
additional_info = getattr(request, 'additional_info', None)
|
|
222
|
+
if additional_info is None:
|
|
223
|
+
return
|
|
224
|
+
order = getattr(additional_info, 'order', None)
|
|
225
|
+
if not order:
|
|
226
|
+
return
|
|
227
|
+
|
|
228
|
+
contexts: List[Dict[str, str]] = []
|
|
229
|
+
|
|
230
|
+
buyer = getattr(order, 'buyer', None)
|
|
231
|
+
if buyer:
|
|
232
|
+
ext_type = _trim_str(getattr(buyer, 'external_user_type', None))
|
|
233
|
+
ext_id = _trim_str(getattr(buyer, 'external_user_id', None))
|
|
234
|
+
has_type = bool(ext_type)
|
|
235
|
+
has_id = bool(ext_id)
|
|
236
|
+
if has_id and not has_type:
|
|
237
|
+
contexts.append(
|
|
238
|
+
_ctx(
|
|
239
|
+
'additionalInfo.order.buyer.externalUserType',
|
|
240
|
+
'externalUserType is required when externalUserId is filled',
|
|
241
|
+
)
|
|
242
|
+
)
|
|
243
|
+
if has_type and not has_id:
|
|
244
|
+
contexts.append(
|
|
245
|
+
_ctx(
|
|
246
|
+
'additionalInfo.order.buyer.externalUserId',
|
|
247
|
+
'externalUserId is required when externalUserType is filled',
|
|
248
|
+
)
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
goods = getattr(order, 'goods', None)
|
|
252
|
+
if isinstance(goods, list) and len(goods) > 0:
|
|
253
|
+
for i, g in enumerate(goods):
|
|
254
|
+
if not g:
|
|
255
|
+
continue
|
|
256
|
+
name = _trim_str(getattr(g, 'name', None))
|
|
257
|
+
if not name:
|
|
258
|
+
contexts.append(
|
|
259
|
+
_ctx(
|
|
260
|
+
f'additionalInfo.order.goods[{i}].name',
|
|
261
|
+
'name is required when goods is filled',
|
|
262
|
+
)
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
shipping_info = getattr(order, 'shipping_info', None)
|
|
266
|
+
if isinstance(shipping_info, list) and len(shipping_info) > 0:
|
|
267
|
+
for i, s in enumerate(shipping_info):
|
|
268
|
+
if not s:
|
|
269
|
+
continue
|
|
270
|
+
first_name = _trim_str(getattr(s, 'first_name', None))
|
|
271
|
+
if not first_name:
|
|
272
|
+
contexts.append(
|
|
273
|
+
_ctx(
|
|
274
|
+
f'additionalInfo.order.shippingInfo[{i}].firstName',
|
|
275
|
+
'firstName is required when shippingInfo is filled',
|
|
276
|
+
)
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
if contexts:
|
|
280
|
+
raise ApiException(status=0, contexts=contexts)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
validation_registry: Dict[str, List[Callable[[Any], None]]] = {
|
|
284
|
+
'CreateOrderByApiRequest': [
|
|
285
|
+
validate_additional_info_required,
|
|
286
|
+
validate_money_value_pattern,
|
|
287
|
+
validate_valid_up_to_create_order_request,
|
|
288
|
+
validate_external_store_id_for_qris,
|
|
289
|
+
validate_sandbox_pay_method_and_pay_option,
|
|
290
|
+
validate_conditional_pay_option_additional_info_create_order_request,
|
|
291
|
+
validate_optional_fields_with_required_nested_create_order_request,
|
|
292
|
+
],
|
|
293
|
+
'CreateOrderByRedirectRequest': [
|
|
294
|
+
validate_additional_info_required,
|
|
295
|
+
validate_money_value_pattern,
|
|
296
|
+
validate_valid_up_to_create_order_request,
|
|
297
|
+
validate_sandbox_pay_method_and_pay_option,
|
|
298
|
+
validate_conditional_pay_option_additional_info_create_order_request,
|
|
299
|
+
validate_optional_fields_with_required_nested_create_order_request,
|
|
300
|
+
],
|
|
301
|
+
'CreateOrderRequest': [
|
|
302
|
+
validate_additional_info_required,
|
|
303
|
+
validate_money_value_pattern,
|
|
304
|
+
validate_valid_up_to_create_order_request,
|
|
305
|
+
validate_external_store_id_for_qris,
|
|
306
|
+
validate_sandbox_pay_method_and_pay_option,
|
|
307
|
+
validate_conditional_pay_option_additional_info_create_order_request,
|
|
308
|
+
validate_optional_fields_with_required_nested_create_order_request,
|
|
309
|
+
],
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def custom_validation(request: Any) -> None:
|
|
314
|
+
"""Run all validators for the request type and aggregate client validation contexts."""
|
|
315
|
+
if request is None:
|
|
316
|
+
return
|
|
317
|
+
|
|
318
|
+
class_name = request.__class__.__name__
|
|
319
|
+
if class_name not in validation_registry:
|
|
320
|
+
return
|
|
321
|
+
|
|
322
|
+
aggregated: List[Dict[str, str]] = []
|
|
323
|
+
for validator in validation_registry[class_name]:
|
|
324
|
+
try:
|
|
325
|
+
validator(request)
|
|
326
|
+
except ApiException as e:
|
|
327
|
+
if e.contexts:
|
|
328
|
+
aggregated.extend(e.contexts)
|
|
329
|
+
else:
|
|
330
|
+
raise
|
|
331
|
+
|
|
332
|
+
if aggregated:
|
|
333
|
+
raise ApiException(status=0, contexts=aggregated)
|
|
@@ -15,10 +15,23 @@
|
|
|
15
15
|
import importlib
|
|
16
16
|
import pkgutil
|
|
17
17
|
import os
|
|
18
|
+
import re
|
|
18
19
|
from typing import List
|
|
19
20
|
|
|
20
21
|
PACKAGE_NAME = 'dana'
|
|
21
22
|
|
|
23
|
+
# Only single path segments that are valid Python identifiers (no traversal / injection).
|
|
24
|
+
_SAFE_PKG_SEGMENT = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _assert_safe_import_segment(name: str, label: str) -> None:
|
|
28
|
+
if not name or not _SAFE_PKG_SEGMENT.fullmatch(name):
|
|
29
|
+
raise ValueError(
|
|
30
|
+
f"Refusing dynamic import: disallowed {label} {name!r} "
|
|
31
|
+
"(expected a single identifier-like package segment)"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
22
35
|
def import_all_models(base_path: str) -> None:
|
|
23
36
|
"""
|
|
24
37
|
Imports all model modules from subdirectories of the specified base path.
|
|
@@ -48,6 +61,8 @@ def import_all_models(base_path: str) -> None:
|
|
|
48
61
|
path_to_domain: List[str] = getattr(subdomain.module_finder, 'path', '')
|
|
49
62
|
domain = os.path.basename(path_to_domain)
|
|
50
63
|
|
|
51
|
-
# Construct the full module path and import
|
|
64
|
+
# Construct the full module path and import (whitelist segments — no dynamic/untrusted strings)
|
|
65
|
+
_assert_safe_import_segment(domain, "domain")
|
|
66
|
+
_assert_safe_import_segment(subdomain.name, "subdomain")
|
|
52
67
|
module_name = f"{PACKAGE_NAME}.{domain}.{subdomain.name}.models"
|
|
53
68
|
importlib.import_module(module_name)
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Copyright 2026 PT Espay Debit Indonesia Koe
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""
|
|
16
|
+
CustomValidation
|
|
17
|
+
|
|
18
|
+
This module provides custom validation functions for Widget API requests.
|
|
19
|
+
Validations are registered in the validation_registry and executed via custom_validation().
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from typing import Any, Callable, Dict, List
|
|
23
|
+
|
|
24
|
+
from dana.utils.date_validation import validate_valid_up_to_date
|
|
25
|
+
from dana.exceptions import ApiException
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _ctx(field: str, message: str) -> Dict[str, str]:
|
|
29
|
+
return {'field': field, 'message': message}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def validate_valid_up_to_widget_payment_request(request: Any) -> None:
|
|
33
|
+
"""Validate validUpTo field in WidgetPaymentRequest."""
|
|
34
|
+
if request is None:
|
|
35
|
+
return
|
|
36
|
+
if hasattr(request, 'valid_up_to') and request.valid_up_to is not None:
|
|
37
|
+
try:
|
|
38
|
+
validate_valid_up_to_date(request.valid_up_to)
|
|
39
|
+
except ValueError as e:
|
|
40
|
+
raise ApiException(status=0, contexts=[
|
|
41
|
+
_ctx('validUpTo', f'validUpTo validation failed: {str(e)}')
|
|
42
|
+
]) from e
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _contains_forbidden_auth_code_delimiters(auth_code: str) -> bool:
|
|
46
|
+
return '&' in auth_code or '=' in auth_code
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def validate_apply_token_auth_code_authorization_code(request: Any) -> None:
|
|
50
|
+
"""authCode must not contain URL query delimiter characters (pasted query string)."""
|
|
51
|
+
if request is None:
|
|
52
|
+
return
|
|
53
|
+
auth_code = getattr(request, 'auth_code', None)
|
|
54
|
+
if auth_code is None:
|
|
55
|
+
return
|
|
56
|
+
s = str(auth_code)
|
|
57
|
+
if s and _contains_forbidden_auth_code_delimiters(s):
|
|
58
|
+
raise ApiException(status=0, contexts=[
|
|
59
|
+
_ctx('authCode', "authCode must not contain URL query delimiter characters ('&' or '=')")
|
|
60
|
+
])
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def validate_apply_token_auth_code_refresh_token(request: Any) -> None:
|
|
64
|
+
"""If authCode is present on refresh-token request, apply the same rule."""
|
|
65
|
+
if request is None:
|
|
66
|
+
return
|
|
67
|
+
auth_code = getattr(request, 'auth_code', None)
|
|
68
|
+
if auth_code is None:
|
|
69
|
+
return
|
|
70
|
+
trimmed = str(auth_code).strip()
|
|
71
|
+
if not trimmed:
|
|
72
|
+
return
|
|
73
|
+
if _contains_forbidden_auth_code_delimiters(trimmed):
|
|
74
|
+
raise ApiException(status=0, contexts=[
|
|
75
|
+
_ctx('authCode', "authCode must not contain URL query delimiter characters ('&' or '=')")
|
|
76
|
+
])
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
validation_registry: Dict[str, List[Callable[[Any], None]]] = {
|
|
80
|
+
'WidgetPaymentRequest': [
|
|
81
|
+
validate_valid_up_to_widget_payment_request,
|
|
82
|
+
],
|
|
83
|
+
'ApplyTokenAuthorizationCodeRequest': [
|
|
84
|
+
validate_apply_token_auth_code_authorization_code,
|
|
85
|
+
],
|
|
86
|
+
'ApplyTokenRefreshTokenRequest': [
|
|
87
|
+
validate_apply_token_auth_code_refresh_token,
|
|
88
|
+
],
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def custom_validation(request: Any) -> None:
|
|
93
|
+
"""Run all validators for the request type and aggregate client validation contexts."""
|
|
94
|
+
if request is None:
|
|
95
|
+
return
|
|
96
|
+
|
|
97
|
+
class_name = request.__class__.__name__
|
|
98
|
+
if class_name not in validation_registry:
|
|
99
|
+
return
|
|
100
|
+
|
|
101
|
+
aggregated: List[Dict[str, str]] = []
|
|
102
|
+
for validator in validation_registry[class_name]:
|
|
103
|
+
try:
|
|
104
|
+
validator(request)
|
|
105
|
+
except ApiException as e:
|
|
106
|
+
if e.contexts:
|
|
107
|
+
aggregated.extend(e.contexts)
|
|
108
|
+
else:
|
|
109
|
+
raise
|
|
110
|
+
|
|
111
|
+
if aggregated:
|
|
112
|
+
raise ApiException(status=0, contexts=aggregated)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dana-python
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.1
|
|
4
4
|
Summary: API Client (SDK) for DANA APIs based on https://dashboard.dana.id/api-docs
|
|
5
5
|
Author-email: DANA Package Manager <package-manager@dana.id>
|
|
6
6
|
Maintainer-email: DANA Package Manager <package-manager@dana.id>
|
|
@@ -12,9 +12,9 @@ Requires-Python: >3.9.1
|
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
License-File: LICENSE
|
|
14
14
|
Requires-Dist: annotated-types==0.7.0
|
|
15
|
-
Requires-Dist: cffi==
|
|
15
|
+
Requires-Dist: cffi==2.0.0
|
|
16
16
|
Requires-Dist: cryptography<46.0.0,>=44.0.2
|
|
17
|
-
Requires-Dist: pycparser==2.
|
|
17
|
+
Requires-Dist: pycparser==2.23
|
|
18
18
|
Requires-Dist: pydantic<3.0.0,>=2.10.6
|
|
19
19
|
Requires-Dist: pydantic-core<3.0.0,>=2.27.2
|
|
20
20
|
Requires-Dist: python-dateutil==2.9.0.post0
|
|
@@ -88,7 +88,7 @@ Before using the SDK, please make sure to set the following environment variable
|
|
|
88
88
|
| ---------------------- | --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
|
|
89
89
|
| `ENV` or `DANA_ENV` | Defines which environment the SDK will use. Possible values: `SANDBOX` or `PRODUCTION`. | `SANDBOX` |
|
|
90
90
|
| `X_PARTNER_ID` | Unique identifier for partner, provided by DANA, also known as `clientId`. | 1970010100000000000000 |
|
|
91
|
-
| `PRIVATE_KEY` | Your private key string. |
|
|
91
|
+
| `PRIVATE_KEY` | Your private key string. | |
|
|
92
92
|
| `PRIVATE_KEY_PATH` | Path to your private key file. If both are set, `PRIVATE_KEY_PATH` is used. | /path/to/your_private_key.pem |
|
|
93
93
|
| `DANA_PUBLIC_KEY` | DANA public key string for parsing webhook. | `-----BEGIN PUBLIC KEY-----MIIBIjANBgkq...Do/QIDAQAB-----END PUBLIC KEY-----` |
|
|
94
94
|
| `DANA_PUBLIC_KEY_PATH` | Path to DANA public key file for parsing webhook. If both set, `DANA_PUBLIC_KEY_PATH is used. | /path/to/dana_public_key.pem |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "dana-python"
|
|
3
|
-
version = "2.1.
|
|
3
|
+
version = "2.1.1"
|
|
4
4
|
readme = "README.md"
|
|
5
5
|
description = "API Client (SDK) for DANA APIs based on https://dashboard.dana.id/api-docs"
|
|
6
6
|
license = "Apache-2.0"
|
|
@@ -14,9 +14,9 @@ maintainers = [
|
|
|
14
14
|
keywords = ["DANA", "DANA ID Docs", "DANA SDK", "DANA Python", "DANA API Client", "DANA Python API Client", "DANA Python SDK"]
|
|
15
15
|
dependencies = [
|
|
16
16
|
"annotated-types==0.7.0",
|
|
17
|
-
"cffi==
|
|
17
|
+
"cffi==2.0.0",
|
|
18
18
|
"cryptography>=44.0.2,<46.0.0",
|
|
19
|
-
"pycparser==2.
|
|
19
|
+
"pycparser==2.23",
|
|
20
20
|
"pydantic>=2.10.6,<3.0.0",
|
|
21
21
|
"pydantic-core>=2.27.2,<3.0.0",
|
|
22
22
|
"python-dateutil==2.9.0.post0",
|
|
@@ -109,4 +109,4 @@ disallow_untyped_calls = true
|
|
|
109
109
|
disallow_incomplete_defs = true
|
|
110
110
|
disallow_untyped_defs = true
|
|
111
111
|
no_implicit_reexport = true
|
|
112
|
-
warn_return_any = true
|
|
112
|
+
warn_return_any = true
|