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.
Files changed (231) hide show
  1. {dana_python-2.1.0/dana_python.egg-info → dana_python-2.1.1}/PKG-INFO +4 -4
  2. {dana_python-2.1.0 → dana_python-2.1.1}/README.md +1 -1
  3. {dana_python-2.1.0 → dana_python-2.1.1}/dana/exceptions.py +5 -1
  4. dana_python-2.1.1/dana/payment_gateway/v1/custom_validation.py +333 -0
  5. {dana_python-2.1.0 → dana_python-2.1.1}/dana/utils/script.py +16 -1
  6. dana_python-2.1.1/dana/widget/v1/custom_validation.py +112 -0
  7. {dana_python-2.1.0 → dana_python-2.1.1/dana_python.egg-info}/PKG-INFO +4 -4
  8. {dana_python-2.1.0 → dana_python-2.1.1}/dana_python.egg-info/requires.txt +2 -2
  9. {dana_python-2.1.0 → dana_python-2.1.1}/pyproject.toml +4 -4
  10. {dana_python-2.1.0 → dana_python-2.1.1}/tests/test_payment_gateway_api.py +132 -0
  11. dana_python-2.1.0/dana/payment_gateway/v1/custom_validation.py +0 -262
  12. dana_python-2.1.0/dana/widget/v1/custom_validation.py +0 -80
  13. {dana_python-2.1.0 → dana_python-2.1.1}/LICENSE +0 -0
  14. {dana_python-2.1.0 → dana_python-2.1.1}/dana/__init__.py +0 -0
  15. {dana_python-2.1.0 → dana_python-2.1.1}/dana/api_client.py +0 -0
  16. {dana_python-2.1.0 → dana_python-2.1.1}/dana/api_response.py +0 -0
  17. {dana_python-2.1.0 → dana_python-2.1.1}/dana/base/__init__.py +0 -0
  18. {dana_python-2.1.0 → dana_python-2.1.1}/dana/base/configuration.py +0 -0
  19. {dana_python-2.1.0 → dana_python-2.1.1}/dana/base/model.py +0 -0
  20. {dana_python-2.1.0 → dana_python-2.1.1}/dana/base/types.py +0 -0
  21. {dana_python-2.1.0 → dana_python-2.1.1}/dana/disbursement/__init__.py +0 -0
  22. {dana_python-2.1.0 → dana_python-2.1.1}/dana/disbursement/v1/__init__.py +0 -0
  23. {dana_python-2.1.0 → dana_python-2.1.1}/dana/disbursement/v1/api/__init__.py +0 -0
  24. {dana_python-2.1.0 → dana_python-2.1.1}/dana/disbursement/v1/api/disbursement_api.py +0 -0
  25. {dana_python-2.1.0 → dana_python-2.1.1}/dana/disbursement/v1/enum.py +0 -0
  26. {dana_python-2.1.0 → dana_python-2.1.1}/dana/disbursement/v1/models/__init__.py +0 -0
  27. {dana_python-2.1.0 → dana_python-2.1.1}/dana/disbursement/v1/models/bank_account_inquiry_request.py +0 -0
  28. {dana_python-2.1.0 → dana_python-2.1.1}/dana/disbursement/v1/models/bank_account_inquiry_request_additional_info.py +0 -0
  29. {dana_python-2.1.0 → dana_python-2.1.1}/dana/disbursement/v1/models/bank_account_inquiry_response.py +0 -0
  30. {dana_python-2.1.0 → dana_python-2.1.1}/dana/disbursement/v1/models/bank_account_inquiry_response_additional_info.py +0 -0
  31. {dana_python-2.1.0 → dana_python-2.1.1}/dana/disbursement/v1/models/dana_account_inquiry_request.py +0 -0
  32. {dana_python-2.1.0 → dana_python-2.1.1}/dana/disbursement/v1/models/dana_account_inquiry_request_additional_info.py +0 -0
  33. {dana_python-2.1.0 → dana_python-2.1.1}/dana/disbursement/v1/models/dana_account_inquiry_response.py +0 -0
  34. {dana_python-2.1.0 → dana_python-2.1.1}/dana/disbursement/v1/models/money.py +0 -0
  35. {dana_python-2.1.0 → dana_python-2.1.1}/dana/disbursement/v1/models/transfer_to_bank_inquiry_status_request.py +0 -0
  36. {dana_python-2.1.0 → dana_python-2.1.1}/dana/disbursement/v1/models/transfer_to_bank_inquiry_status_response.py +0 -0
  37. {dana_python-2.1.0 → dana_python-2.1.1}/dana/disbursement/v1/models/transfer_to_bank_request.py +0 -0
  38. {dana_python-2.1.0 → dana_python-2.1.1}/dana/disbursement/v1/models/transfer_to_bank_request_additional_info.py +0 -0
  39. {dana_python-2.1.0 → dana_python-2.1.1}/dana/disbursement/v1/models/transfer_to_bank_response.py +0 -0
  40. {dana_python-2.1.0 → dana_python-2.1.1}/dana/disbursement/v1/models/transfer_to_dana_inquiry_status_request.py +0 -0
  41. {dana_python-2.1.0 → dana_python-2.1.1}/dana/disbursement/v1/models/transfer_to_dana_inquiry_status_response.py +0 -0
  42. {dana_python-2.1.0 → dana_python-2.1.1}/dana/disbursement/v1/models/transfer_to_dana_request.py +0 -0
  43. {dana_python-2.1.0 → dana_python-2.1.1}/dana/disbursement/v1/models/transfer_to_dana_request_additional_info.py +0 -0
  44. {dana_python-2.1.0 → dana_python-2.1.1}/dana/disbursement/v1/models/transfer_to_dana_response.py +0 -0
  45. {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/__init__.py +0 -0
  46. {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/__init__.py +0 -0
  47. {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/api/__init__.py +0 -0
  48. {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/api/merchant_management_api.py +0 -0
  49. {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/enum.py +0 -0
  50. {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/__init__.py +0 -0
  51. {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/address_info.py +0 -0
  52. {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/business_docs.py +0 -0
  53. {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/create_division_request.py +0 -0
  54. {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/create_division_request_ext_info.py +0 -0
  55. {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/create_division_response.py +0 -0
  56. {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/create_division_response_response.py +0 -0
  57. {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/create_division_response_response_body.py +0 -0
  58. {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/create_division_response_response_head.py +0 -0
  59. {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/create_shop_request.py +0 -0
  60. {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/create_shop_response.py +0 -0
  61. {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/create_shop_response_response.py +0 -0
  62. {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/create_shop_response_response_body.py +0 -0
  63. {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/create_shop_response_response_head.py +0 -0
  64. {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/division_resource_info.py +0 -0
  65. {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/merchant_resource_information.py +0 -0
  66. {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/mobile_no_info.py +0 -0
  67. {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/pic_info.py +0 -0
  68. {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/query_division_request.py +0 -0
  69. {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/query_division_response.py +0 -0
  70. {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/query_division_response_response.py +0 -0
  71. {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/query_division_response_response_body.py +0 -0
  72. {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/query_division_response_response_head.py +0 -0
  73. {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/query_merchant_resource_request.py +0 -0
  74. {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/query_merchant_resource_response.py +0 -0
  75. {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/query_merchant_resource_response_response.py +0 -0
  76. {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/query_merchant_resource_response_response_body.py +0 -0
  77. {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/query_merchant_resource_response_response_head.py +0 -0
  78. {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/query_shop_request.py +0 -0
  79. {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/query_shop_response.py +0 -0
  80. {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/query_shop_response_response.py +0 -0
  81. {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/query_shop_response_response_body.py +0 -0
  82. {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/query_shop_response_response_head.py +0 -0
  83. {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/result_info.py +0 -0
  84. {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/shop_resource_info.py +0 -0
  85. {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/update_division_request.py +0 -0
  86. {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/update_division_response.py +0 -0
  87. {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/update_division_response_response.py +0 -0
  88. {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/update_division_response_response_body.py +0 -0
  89. {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/update_division_response_response_head.py +0 -0
  90. {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/update_shop_request.py +0 -0
  91. {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/update_shop_response.py +0 -0
  92. {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/update_shop_response_response.py +0 -0
  93. {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/update_shop_response_response_head.py +0 -0
  94. {dana_python-2.1.0 → dana_python-2.1.1}/dana/merchant_management/v1/models/user_name.py +0 -0
  95. {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/__init__.py +0 -0
  96. {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/__init__.py +0 -0
  97. {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/api/__init__.py +0 -0
  98. {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/api/payment_gateway_api.py +0 -0
  99. {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/enum.py +0 -0
  100. {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/__init__.py +0 -0
  101. {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/actor_context.py +0 -0
  102. {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/amount_detail.py +0 -0
  103. {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/audit_info.py +0 -0
  104. {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/buyer.py +0 -0
  105. {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/cancel_order_request.py +0 -0
  106. {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/cancel_order_response.py +0 -0
  107. {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/consult_pay_payment_info.py +0 -0
  108. {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/consult_pay_request.py +0 -0
  109. {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/consult_pay_request_additional_info.py +0 -0
  110. {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/consult_pay_response.py +0 -0
  111. {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/create_order_by_api_additional_info.py +0 -0
  112. {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/create_order_by_api_request.py +0 -0
  113. {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/create_order_by_redirect_additional_info.py +0 -0
  114. {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/create_order_by_redirect_request.py +0 -0
  115. {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/create_order_response.py +0 -0
  116. {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/create_order_response_additional_info.py +0 -0
  117. {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/env_info.py +0 -0
  118. {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/goods.py +0 -0
  119. {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/money.py +0 -0
  120. {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/order_api_object.py +0 -0
  121. {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/order_redirect_object.py +0 -0
  122. {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/pay_option_additional_info.py +0 -0
  123. {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/pay_option_detail.py +0 -0
  124. {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/pay_option_info.py +0 -0
  125. {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/payment_view.py +0 -0
  126. {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/promo_info.py +0 -0
  127. {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/query_payment_request.py +0 -0
  128. {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/query_payment_response.py +0 -0
  129. {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/query_payment_response_additional_info.py +0 -0
  130. {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/refund_option_bill.py +0 -0
  131. {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/refund_order_request.py +0 -0
  132. {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/refund_order_request_additional_info.py +0 -0
  133. {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/refund_order_response.py +0 -0
  134. {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/seller.py +0 -0
  135. {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/shipping_info.py +0 -0
  136. {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/status_detail.py +0 -0
  137. {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/time_detail.py +0 -0
  138. {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/url_param.py +0 -0
  139. {dana_python-2.1.0 → dana_python-2.1.1}/dana/payment_gateway/v1/models/virtual_account_info.py +0 -0
  140. {dana_python-2.1.0 → dana_python-2.1.1}/dana/rest.py +0 -0
  141. {dana_python-2.1.0 → dana_python-2.1.1}/dana/utils/date_validation.py +0 -0
  142. {dana_python-2.1.0 → dana_python-2.1.1}/dana/utils/models.py +0 -0
  143. {dana_python-2.1.0 → dana_python-2.1.1}/dana/utils/open_api_configuration.py +0 -0
  144. {dana_python-2.1.0 → dana_python-2.1.1}/dana/utils/open_api_header.py +0 -0
  145. {dana_python-2.1.0 → dana_python-2.1.1}/dana/utils/snap_configuration.py +0 -0
  146. {dana_python-2.1.0 → dana_python-2.1.1}/dana/utils/snap_header.py +0 -0
  147. {dana_python-2.1.0 → dana_python-2.1.1}/dana/utils/url.py +0 -0
  148. {dana_python-2.1.0 → dana_python-2.1.1}/dana/webhook/__init__.py +0 -0
  149. {dana_python-2.1.0 → dana_python-2.1.1}/dana/webhook/finish_notify_payment_info.py +0 -0
  150. {dana_python-2.1.0 → dana_python-2.1.1}/dana/webhook/finish_notify_request.py +0 -0
  151. {dana_python-2.1.0 → dana_python-2.1.1}/dana/webhook/finish_notify_request_additional_info.py +0 -0
  152. {dana_python-2.1.0 → dana_python-2.1.1}/dana/webhook/finish_notify_response.py +0 -0
  153. {dana_python-2.1.0 → dana_python-2.1.1}/dana/webhook/money.py +0 -0
  154. {dana_python-2.1.0 → dana_python-2.1.1}/dana/webhook/pay_option_info.py +0 -0
  155. {dana_python-2.1.0 → dana_python-2.1.1}/dana/webhook/shop_info.py +0 -0
  156. {dana_python-2.1.0 → dana_python-2.1.1}/dana/webhook/webhook.py +0 -0
  157. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/__init__.py +0 -0
  158. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/__init__.py +0 -0
  159. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/api/__init__.py +0 -0
  160. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/api/widget_api.py +0 -0
  161. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/enum.py +0 -0
  162. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/__init__.py +0 -0
  163. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/account_info.py +0 -0
  164. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/account_unbinding_request.py +0 -0
  165. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/account_unbinding_request_additional_info.py +0 -0
  166. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/account_unbinding_response.py +0 -0
  167. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/amount_detail.py +0 -0
  168. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/apply_ott_request.py +0 -0
  169. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/apply_ott_request_additional_info.py +0 -0
  170. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/apply_ott_response.py +0 -0
  171. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/apply_ott_response_user_resources_inner.py +0 -0
  172. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/apply_token_authorization_code_request.py +0 -0
  173. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/apply_token_refresh_token_request.py +0 -0
  174. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/apply_token_response.py +0 -0
  175. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/apply_token_response_additional_info.py +0 -0
  176. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/apply_token_response_additional_info_user_info.py +0 -0
  177. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/balance_inquiry_request.py +0 -0
  178. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/balance_inquiry_request_additional_info.py +0 -0
  179. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/balance_inquiry_response.py +0 -0
  180. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/buyer.py +0 -0
  181. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/cancel_order_request.py +0 -0
  182. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/cancel_order_response.py +0 -0
  183. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/env_info.py +0 -0
  184. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/goods.py +0 -0
  185. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/international_order_info.py +0 -0
  186. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/international_order_info_exchange_rate.py +0 -0
  187. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/money.py +0 -0
  188. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/oauth2_url_data.py +0 -0
  189. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/oauth2_url_data_seamless_data.py +0 -0
  190. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/order.py +0 -0
  191. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/pay_option_detail.py +0 -0
  192. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/pay_option_detail_additional_info.py +0 -0
  193. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/pay_option_info.py +0 -0
  194. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/payment_promo_info.py +0 -0
  195. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/payment_view.py +0 -0
  196. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/query_payment_request.py +0 -0
  197. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/query_payment_response.py +0 -0
  198. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/query_payment_response_additional_info.py +0 -0
  199. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/query_user_profile_request.py +0 -0
  200. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/query_user_profile_response.py +0 -0
  201. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/query_user_profile_response_response.py +0 -0
  202. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/query_user_profile_response_response_body.py +0 -0
  203. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/query_user_profile_response_response_head.py +0 -0
  204. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/refund_order_request.py +0 -0
  205. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/refund_order_request_additional_info.py +0 -0
  206. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/refund_order_response.py +0 -0
  207. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/refund_promo_info.py +0 -0
  208. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/result_info.py +0 -0
  209. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/seller.py +0 -0
  210. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/service_info.py +0 -0
  211. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/shipping_info.py +0 -0
  212. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/status_detail.py +0 -0
  213. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/time_detail.py +0 -0
  214. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/url_param.py +0 -0
  215. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/user_resource_info.py +0 -0
  216. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/virtual_account_info.py +0 -0
  217. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/widget_payment_request.py +0 -0
  218. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/widget_payment_request_additional_info.py +0 -0
  219. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/models/widget_payment_response.py +0 -0
  220. {dana_python-2.1.0 → dana_python-2.1.1}/dana/widget/v1/util.py +0 -0
  221. {dana_python-2.1.0 → dana_python-2.1.1}/dana_python.egg-info/SOURCES.txt +0 -0
  222. {dana_python-2.1.0 → dana_python-2.1.1}/dana_python.egg-info/dependency_links.txt +0 -0
  223. {dana_python-2.1.0 → dana_python-2.1.1}/dana_python.egg-info/top_level.txt +0 -0
  224. {dana_python-2.1.0 → dana_python-2.1.1}/setup.cfg +0 -0
  225. {dana_python-2.1.0 → dana_python-2.1.1}/tests/test_disbursement_api.py +0 -0
  226. {dana_python-2.1.0 → dana_python-2.1.1}/tests/test_merchant_management_api.py +0 -0
  227. {dana_python-2.1.0 → dana_python-2.1.1}/tests/test_payment_gateway_with_automation.py +0 -0
  228. {dana_python-2.1.0 → dana_python-2.1.1}/tests/test_snap_header.py +0 -0
  229. {dana_python-2.1.0 → dana_python-2.1.1}/tests/test_webhook.py +0 -0
  230. {dana_python-2.1.0 → dana_python-2.1.1}/tests/test_widget_api.py +0 -0
  231. {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.0
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==1.17.1
15
+ Requires-Dist: cffi==2.0.0
16
16
  Requires-Dist: cryptography<46.0.0,>=44.0.2
17
- Requires-Dist: pycparser==2.22
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. | `-----BEGIN PRIVATE KEY-----MIIBVgIBADANBg...LsvTqw==-----END PRIVATE KEY-----` |
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. | `-----BEGIN PRIVATE KEY-----MIIBVgIBADANBg...LsvTqw==-----END PRIVATE KEY-----` |
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.0
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==1.17.1
15
+ Requires-Dist: cffi==2.0.0
16
16
  Requires-Dist: cryptography<46.0.0,>=44.0.2
17
- Requires-Dist: pycparser==2.22
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. | `-----BEGIN PRIVATE KEY-----MIIBVgIBADANBg...LsvTqw==-----END PRIVATE KEY-----` |
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,7 +1,7 @@
1
1
  annotated-types==0.7.0
2
- cffi==1.17.1
2
+ cffi==2.0.0
3
3
  cryptography<46.0.0,>=44.0.2
4
- pycparser==2.22
4
+ pycparser==2.23
5
5
  pydantic<3.0.0,>=2.10.6
6
6
  pydantic-core<3.0.0,>=2.27.2
7
7
  python-dateutil==2.9.0.post0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "dana-python"
3
- version = "2.1.0"
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==1.17.1",
17
+ "cffi==2.0.0",
18
18
  "cryptography>=44.0.2,<46.0.0",
19
- "pycparser==2.22",
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