django-forms-workflows 0.77.3__tar.gz → 0.77.5__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. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/CHANGELOG.md +46 -0
  2. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/PKG-INFO +4 -2
  3. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/payments/__init__.py +8 -1
  4. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/payments/base.py +24 -0
  5. django_forms_workflows-0.77.5/django_forms_workflows/payments/checkout.py +84 -0
  6. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/payments/stripe_provider.py +38 -13
  7. django_forms_workflows-0.77.5/django_forms_workflows/payments/views.py +484 -0
  8. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/payment_collect.html +8 -2
  9. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/payment_error.html +6 -3
  10. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/views.py +10 -0
  11. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/pyproject.toml +5 -4
  12. django_forms_workflows-0.77.3/django_forms_workflows/payments/views.py +0 -358
  13. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/LICENSE +0 -0
  14. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/README.md +0 -0
  15. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/__init__.py +0 -0
  16. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/admin.py +0 -0
  17. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/api_urls.py +0 -0
  18. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/api_views.py +0 -0
  19. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/apps.py +0 -0
  20. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/callback_registry.py +0 -0
  21. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/conditions.py +0 -0
  22. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/context_processors.py +0 -0
  23. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/data_sources/__init__.py +0 -0
  24. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/data_sources/base.py +0 -0
  25. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/data_sources/database_source.py +0 -0
  26. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/data_sources/ldap_source.py +0 -0
  27. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/data_sources/user_source.py +0 -0
  28. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/diff_views.py +0 -0
  29. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/email_backends/__init__.py +0 -0
  30. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/email_backends/gmail_api.py +0 -0
  31. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/form_builder_urls.py +0 -0
  32. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/form_builder_views.py +0 -0
  33. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/forms.py +0 -0
  34. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/handlers/__init__.py +0 -0
  35. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/handlers/api_handler.py +0 -0
  36. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/handlers/base.py +0 -0
  37. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/handlers/database_handler.py +0 -0
  38. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/handlers/email_handler.py +0 -0
  39. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/handlers/executor.py +0 -0
  40. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/handlers/file_handler.py +0 -0
  41. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/handlers/ldap_handler.py +0 -0
  42. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/history.py +0 -0
  43. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/ldap_backend.py +0 -0
  44. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/management/commands/create_default_templates.py +0 -0
  45. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/management/commands/pull_forms.py +0 -0
  46. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/management/commands/push_forms.py +0 -0
  47. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/management/commands/seed_farm_demo.py +0 -0
  48. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/management/commands/seed_pcn_form.py +0 -0
  49. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/management/commands/seed_prefill_sources.py +0 -0
  50. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/management/commands/sync_ldap_profiles.py +0 -0
  51. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/management/commands/sync_ldap_users.py +0 -0
  52. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/management/commands/test_db_connection.py +0 -0
  53. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0001_initial.py +0 -0
  54. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0002_prefillsource_alter_formfield_prefill_source_and_more.py +0 -0
  55. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0003_postsubmissionaction.py +0 -0
  56. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0004_formtemplate.py +0 -0
  57. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0005_advanced_conditional_logic.py +0 -0
  58. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0006_workflowdefinition_visual_workflow_data_and_more.py +0 -0
  59. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0007_add_userprofile_ldap_enhancements.py +0 -0
  60. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0008_add_file_workflow_models.py +0 -0
  61. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0009_add_formfield_readonly.py +0 -0
  62. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0010_add_prefillsource_template_fields.py +0 -0
  63. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0011_postsubmissionaction_email_body_template_and_more.py +0 -0
  64. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0012_add_approval_step_fields.py +0 -0
  65. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0013_add_database_query_key.py +0 -0
  66. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0014_add_form_category.py +0 -0
  67. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0015_add_workflowstage_staged_approvals.py +0 -0
  68. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0016_add_pdf_generation.py +0 -0
  69. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0017_add_notification_batching.py +0 -0
  70. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0018_add_form_category_parent.py +0 -0
  71. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0019_add_ldap_group_profile.py +0 -0
  72. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0020_add_bulk_export_field.py +0 -0
  73. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0021_alter_pendingnotification_scheduled_for_and_more.py +0 -0
  74. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0022_add_bulk_pdf_export.py +0 -0
  75. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0023_add_performance_indexes.py +0 -0
  76. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0024_add_allow_resubmit.py +0 -0
  77. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0025_workflowstage_approve_label.py +0 -0
  78. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0026_alter_formfield_width.py +0 -0
  79. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0027_formsubmission_form_data_gin.py +0 -0
  80. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0028_workflowstagegroupconfig.py +0 -0
  81. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0029_formfield_workflow_stage_fk_drop_groupconfig.py +0 -0
  82. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0030_formfield_phone_type.py +0 -0
  83. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0031_sub_workflows.py +0 -0
  84. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0032_approved_pending_detached_sub_workflow.py +0 -0
  85. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0033_add_reject_parent_to_subworkflowdefinition.py +0 -0
  86. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0034_add_notificationlog.py +0 -0
  87. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0035_migrate_prefill_source_strings.py +0 -0
  88. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0036_add_workflowstage_auto_created.py +0 -0
  89. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0037_migrate_flat_workflows_to_staged.py +0 -0
  90. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0038_remove_legacy_prefill_source_and_approval_step.py +0 -0
  91. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0039_update_help_text.py +0 -0
  92. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0040_migrate_deprecated_features.py +0 -0
  93. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0041_remove_deprecated_fields.py +0 -0
  94. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0042_add_sub_workflow_section_label.py +0 -0
  95. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0043_fix_form_data_decimal_encoding.py +0 -0
  96. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0044_remove_subworkflowinstance_label.py +0 -0
  97. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0045_stageapprovalgroup_workflow_name_label.py +0 -0
  98. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0046_workflow_definition_one_to_many.py +0 -0
  99. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0047_add_hide_approval_history.py +0 -0
  100. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0048_workflowdefinition_trigger_conditions_and_more.py +0 -0
  101. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0049_formfield_multifile_type.py +0 -0
  102. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0050_workflowdefinition_collapse_parallel_stages.py +0 -0
  103. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0051_formdefinition_is_listed.py +0 -0
  104. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0052_dynamic_assignee_and_form_field_notifications.py +0 -0
  105. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0053_stageformfieldnotification_static_emails.py +0 -0
  106. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0054_rename_approved_pending_to_pending_approval.py +0 -0
  107. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0055_add_send_back.py +0 -0
  108. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0056_calculated_formula_spreadsheet.py +0 -0
  109. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0057_add_currency_field_type.py +0 -0
  110. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0058_rename_assignee_email_field_add_lookup_type.py +0 -0
  111. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0059_add_multiselect_list_field_type.py +0 -0
  112. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0060_alter_workflowstage_assignee_lookup_type_helptext.py +0 -0
  113. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0061_add_country_us_state_field_types.py +0 -0
  114. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0062_add_batch_import_to_formdefinition.py +0 -0
  115. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0063_formdefinition_api_enabled_apitoken.py +0 -0
  116. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0064_add_reviewer_groups.py +0 -0
  117. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0065_workflownotification.py +0 -0
  118. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0066_add_notify_submitter_to_workflownotification.py +0 -0
  119. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0067_migrate_legacy_notify_flags.py +0 -0
  120. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0068_drop_legacy_notify_fields.py +0 -0
  121. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0069_add_signature_field_type.py +0 -0
  122. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0070_workflowstage_allow_edit_form_data.py +0 -0
  123. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0071_alter_pendingnotification_notification_type.py +0 -0
  124. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0072_add_change_history.py +0 -0
  125. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0073_add_stage_notify_assignee_on_final_decision.py +0 -0
  126. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0074_add_notification_rule.py +0 -0
  127. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0075_migrate_to_notification_rules.py +0 -0
  128. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0076_update_notification_event_choices.py +0 -0
  129. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0077_drop_legacy_notification_models.py +0 -0
  130. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0078_allow_anonymous_submissions.py +0 -0
  131. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0079_webhookendpoint_webhookdeliverylog.py +0 -0
  132. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0080_add_rating_matrix_address_slider_fields_and_form_controls.py +0 -0
  133. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0081_add_success_page_fields.py +0 -0
  134. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0082_add_document_template_model.py +0 -0
  135. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0083_workflowdefinition_start_trigger.py +0 -0
  136. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0084_add_shared_option_list.py +0 -0
  137. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0085_add_payment_system.py +0 -0
  138. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0086_add_embed_enabled.py +0 -0
  139. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0087_notificationrule_use_triggering_stage.py +0 -0
  140. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0088_notificationrule_body_template_and_events.py +0 -0
  141. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0089_workflowstage_hide_comment_field.py +0 -0
  142. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0090_alter_stageapprovalgroup_options_and_more.py +0 -0
  143. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0091_add_uuid_fields.py +0 -0
  144. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0092_add_user_notification_preference.py +0 -0
  145. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0093_normalize_conditional_operators.py +0 -0
  146. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0094_formfield_show_help_text_in_detail.py +0 -0
  147. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0095_notificationrule_cc_fields.py +0 -0
  148. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0096_notificationlog_cc_bcc_emails.py +0 -0
  149. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0097_notificationlog_delivery_reconciliation.py +0 -0
  150. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/__init__.py +0 -0
  151. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/models.py +0 -0
  152. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/payments/registry.py +0 -0
  153. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/reporting_views.py +0 -0
  154. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/signals.py +0 -0
  155. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/sso_backends.py +0 -0
  156. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/sso_urls.py +0 -0
  157. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/sso_views.py +0 -0
  158. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/static/django_forms_workflows/api/swagger-ui-bundle.js +0 -0
  159. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/static/django_forms_workflows/api/swagger-ui.css +0 -0
  160. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/static/django_forms_workflows/css/forms.css +0 -0
  161. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/static/django_forms_workflows/js/admin_notification_rule_stage_filter.js +0 -0
  162. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/static/django_forms_workflows/js/dfw-embed.js +0 -0
  163. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/static/django_forms_workflows/js/form-builder-api.js +0 -0
  164. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/static/django_forms_workflows/js/form-builder-canvas.js +0 -0
  165. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/static/django_forms_workflows/js/form-builder-history.js +0 -0
  166. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/static/django_forms_workflows/js/form-builder-property-editor.js +0 -0
  167. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/static/django_forms_workflows/js/form-builder-store.js +0 -0
  168. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/static/django_forms_workflows/js/form-builder.js +0 -0
  169. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/static/django_forms_workflows/js/form-enhancements.js +0 -0
  170. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/static/django_forms_workflows/js/payment-stripe.js +0 -0
  171. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/static/django_forms_workflows/js/signature-pad.js +0 -0
  172. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/static/django_forms_workflows/js/workflow-builder-api.js +0 -0
  173. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/static/django_forms_workflows/js/workflow-builder-store.js +0 -0
  174. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/static/django_forms_workflows/js/workflow-builder.js +0 -0
  175. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/sync_api.py +0 -0
  176. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/sync_views.py +0 -0
  177. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/tasks.py +0 -0
  178. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/admin/django_forms_workflows/_sync_diff.html +0 -0
  179. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/admin/django_forms_workflows/diff_forms.html +0 -0
  180. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/admin/django_forms_workflows/form_builder.html +0 -0
  181. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/admin/django_forms_workflows/formdef_change_form.html +0 -0
  182. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/admin/django_forms_workflows/formdefinition/change_list.html +0 -0
  183. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/admin/django_forms_workflows/sync_import.html +0 -0
  184. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/admin/django_forms_workflows/sync_pull.html +0 -0
  185. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/admin/django_forms_workflows/sync_push.html +0 -0
  186. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/admin/django_forms_workflows/workflow_builder.html +0 -0
  187. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/_category_node.html +0 -0
  188. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/_field_value.html +0 -0
  189. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/_form_data_rows.html +0 -0
  190. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/analytics_dashboard.html +0 -0
  191. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/api/docs.html +0 -0
  192. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/approval_inbox.html +0 -0
  193. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/approve.html +0 -0
  194. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/base.html +0 -0
  195. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/batch_import_result.html +0 -0
  196. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/completed_approvals.html +0 -0
  197. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/discard_draft_confirm.html +0 -0
  198. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/embed_base.html +0 -0
  199. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/embed_success.html +0 -0
  200. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/form_categories.html +0 -0
  201. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/form_embed.html +0 -0
  202. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/form_list.html +0 -0
  203. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/form_submit.html +0 -0
  204. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/my_submissions.html +0 -0
  205. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/notification_preferences.html +0 -0
  206. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/public_submission_confirmation.html +0 -0
  207. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/rate_limited.html +0 -0
  208. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/reassign_task.html +0 -0
  209. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/resubmit_confirm.html +0 -0
  210. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/sso/login.html +0 -0
  211. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/sub_workflow_detail.html +0 -0
  212. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/submission_bulk_pdf.html +0 -0
  213. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/submission_detail.html +0 -0
  214. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/submission_pdf.html +0 -0
  215. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/submission_success.html +0 -0
  216. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/withdraw_confirm.html +0 -0
  217. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/emails/approval_notification.html +0 -0
  218. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/emails/approval_reminder.html +0 -0
  219. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/emails/approval_request.html +0 -0
  220. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/emails/email_styles.html +0 -0
  221. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/emails/escalation_notification.html +0 -0
  222. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/emails/notification_digest.html +0 -0
  223. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/emails/rejection_notification.html +0 -0
  224. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/emails/submission_notification.html +0 -0
  225. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/registration/login.html +0 -0
  226. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templatetags/__init__.py +0 -0
  227. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templatetags/forms_workflows_tags.py +0 -0
  228. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/urls.py +0 -0
  229. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/utils.py +0 -0
  230. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/workflow_builder_views.py +0 -0
  231. {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/workflow_engine.py +0 -0
@@ -7,6 +7,52 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ### Security
11
+ - **Payment endpoints hardened against unauthorized access and unsafe state
12
+ transitions (issue #27, supersedes #4).** All checkout endpoints
13
+ (`initiate`, `confirm`, `return`, `cancel`) now enforce one access policy
14
+ before touching records or providers: an authenticated submitter may act
15
+ only on their own submission, and an anonymous public-form submitter needs
16
+ a short-lived (`FORMS_WORKFLOWS_PAYMENT_CHECKOUT_TTL`, default 1 hour),
17
+ unguessable session-bound checkout credential issued when the form post
18
+ enters `pending_payment`. Guessing a numeric submission or payment-record
19
+ ID now yields a non-disclosing 404. Payment initiation is idempotent per
20
+ attempt — repeated GETs reuse the single open `PaymentRecord` and its
21
+ stable idempotency key (now forwarded to Stripe's `PaymentIntent.create`)
22
+ instead of creating uncontrolled attempts; a legitimate retry after
23
+ cancel/failure starts a fresh key. Cancellation is a CSRF-protected
24
+ `POST`-only action restricted to `pending_payment` submissions and can no
25
+ longer revert a completed/submitted/approved submission to `draft`.
26
+ Stripe webhooks now **fail closed**: events are rejected (HTTP 400) when
27
+ `STRIPE_WEBHOOK_SECRET` is unset or the signature is invalid, and the
28
+ provider interface requires the same authenticated-event guarantee from
29
+ all providers via `WebhookVerificationError`. Replayed or out-of-order
30
+ events no longer roll payment state backward (`completed`/`refunded` are
31
+ terminal; only `completed → refunded` is allowed) and duplicate deliveries
32
+ create no duplicate workflow tasks. `payment_collect.html` and
33
+ `payment_error.html` cancel actions are now POST forms.
34
+
35
+ - **`social-auth-core` 4.8.3 → 5.1.1 in `poetry.lock`.** Closes five Dependabot
36
+ advisories against the locked version: GHSA-vqg6-3fw6-j9jg (session fixation),
37
+ GHSA-3c93-f73f-qc9h (VK App backend accepted unsigned callback data),
38
+ GHSA-x7qq-23vw-7pfg (login CSRF in the LoginRadius backend),
39
+ GHSA-fp7w-m676-w7gc (account takeover via identity binding in the Vend backend)
40
+ and GHSA-vq6g-g6c7-5f2j (improper authentication). `social-auth-app-django`
41
+ moves 5.7.0 → 6.0.1 (5.x pins `social-auth-core ~=4.8`) and `requests`
42
+ 2.33.0 → 2.34.2 (required by `social-auth-core` 5.x).
43
+ - **`sso` / `all` extras now require `social-auth-app-django >= 6.0` and
44
+ `social-auth-core >= 5.1.1`**, so `pip install django-forms-workflows[sso]`
45
+ can no longer resolve to a vulnerable release. `social-auth-app-django` 6.x
46
+ requires Django 5.2+, which matches the documented minimum; the core package
47
+ itself still allows Django 5.1.
48
+
49
+ ### Fixed
50
+ - **CI test job on `main` has failed since 59ae737.** `tests/settings.py` lists
51
+ `tests.ldap_lookup_test_app` in `INSTALLED_APPS`, but the app package was
52
+ never committed, so every CI run died with
53
+ `ModuleNotFoundError: No module named 'tests.ldap_lookup_test_app'`. The
54
+ package is now in the repo.
55
+
10
56
  ## [0.77.0] - 2026-08-17
11
57
 
12
58
  Some entries below are marked *(shipped in 0.76.x)* — they describe changes that
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-forms-workflows
3
- Version: 0.77.3
3
+ Version: 0.77.5
4
4
  Summary: Enterprise-grade, database-driven form builder with approval workflows and external data integration
5
5
  License: LGPL-3.0-only
6
6
  License-File: LICENSE
@@ -15,6 +15,7 @@ Classifier: Programming Language :: Python :: 3.11
15
15
  Classifier: Programming Language :: Python :: 3.12
16
16
  Classifier: Programming Language :: Python :: 3.13
17
17
  Classifier: Programming Language :: Python :: 3.14
18
+ Classifier: Programming Language :: Python :: 3.15
18
19
  Provides-Extra: all
19
20
  Provides-Extra: dev
20
21
  Provides-Extra: docs
@@ -55,7 +56,8 @@ Requires-Dist: python3-saml (>=1.16) ; extra == "sso" or extra == "saml" or extr
55
56
  Requires-Dist: requests (>=2.31)
56
57
  Requires-Dist: ruff (>=0.4) ; extra == "dev"
57
58
  Requires-Dist: segno (>=1.6) ; extra == "qr" or extra == "all"
58
- Requires-Dist: social-auth-app-django (>=5.4) ; extra == "sso" or extra == "all"
59
+ Requires-Dist: social-auth-app-django (>=6.0) ; extra == "sso" or extra == "all"
60
+ Requires-Dist: social-auth-core (>=5.1.1) ; extra == "sso" or extra == "all"
59
61
  Requires-Dist: sphinx (>=7.0) ; extra == "docs"
60
62
  Requires-Dist: sphinx-autodoc-typehints (>=1.25) ; extra == "docs"
61
63
  Requires-Dist: sphinx-rtd-theme (>=2.0) ; extra == "docs"
@@ -12,7 +12,13 @@ apps can add ECSI, PayPal, Authorize.Net, or any other provider
12
12
  without modifying the library itself.
13
13
  """
14
14
 
15
- from .base import PaymentFlow, PaymentProvider, PaymentResult, PaymentStatus
15
+ from .base import (
16
+ PaymentFlow,
17
+ PaymentProvider,
18
+ PaymentResult,
19
+ PaymentStatus,
20
+ WebhookVerificationError,
21
+ )
16
22
  from .registry import (
17
23
  clear,
18
24
  get_available_providers,
@@ -27,6 +33,7 @@ __all__ = [
27
33
  "PaymentProvider",
28
34
  "PaymentResult",
29
35
  "PaymentStatus",
36
+ "WebhookVerificationError",
30
37
  "clear",
31
38
  "get_available_providers",
32
39
  "get_provider",
@@ -47,6 +47,17 @@ class PaymentStatus(Enum):
47
47
  REFUNDED = "refunded"
48
48
 
49
49
 
50
+ class WebhookVerificationError(Exception):
51
+ """
52
+ Raised by a provider's ``handle_webhook()`` when the event payload
53
+ cannot be authenticated (e.g. a missing/invalid signature, or a
54
+ required verification secret that is not configured).
55
+
56
+ Providers must raise this instead of processing unverified payloads:
57
+ the webhook endpoint fails closed and discards the event.
58
+ """
59
+
60
+
50
61
  @dataclass
51
62
  class PaymentResult:
52
63
  """Returned by create_payment() and confirm_payment()."""
@@ -93,12 +104,19 @@ class PaymentProvider(ABC):
93
104
  submission_id: int,
94
105
  description: str = "",
95
106
  metadata: dict[str, Any] | None = None,
107
+ idempotency_key: str | None = None,
96
108
  ) -> PaymentResult:
97
109
  """
98
110
  Initiate a payment.
99
111
 
100
112
  For INLINE providers: creates a payment intent, returns client_secret.
101
113
  For REDIRECT providers: creates a session, returns redirect_url.
114
+
115
+ ``idempotency_key`` is stable for a single payment attempt (one
116
+ ``PaymentRecord``). Providers that support request idempotency
117
+ should forward it so a retried initiation cannot create duplicate
118
+ provider-side charges. A new attempt (after a cancellation or
119
+ failure) is issued a fresh key.
102
120
  """
103
121
 
104
122
  @abstractmethod
@@ -121,6 +139,12 @@ class PaymentProvider(ABC):
121
139
 
122
140
  Override if the provider sends server-to-server callbacks.
123
141
  Returns None if the event is not relevant.
142
+
143
+ Implementations MUST authenticate the event before returning a
144
+ result (e.g. verify a signature or shared secret) and raise
145
+ :class:`WebhookVerificationError` when verification fails or the
146
+ required verification secret is not configured. Unverified
147
+ payloads must never produce a state-changing result.
124
148
  """
125
149
  return None
126
150
 
@@ -0,0 +1,84 @@
1
+ """
2
+ Checkout authorization for the payment flow.
3
+
4
+ Payment endpoints act on a :class:`~django_forms_workflows.models.FormSubmission`
5
+ that is in ``pending_payment``. Access is governed by exactly one policy:
6
+
7
+ * **Owned submissions** — only the authenticated submitter may act on them.
8
+ A numeric submission or payment-record ID alone grants nothing.
9
+ * **Anonymous (public-form) submissions** — the browser that just posted the
10
+ public form is issued a *checkout credential*: an unguessable token stored
11
+ in its session and bound to the submission ID, valid for a short time
12
+ (``FORMS_WORKFLOWS_PAYMENT_CHECKOUT_TTL`` seconds, default one hour).
13
+
14
+ The credential is granted by the submit views when a submission enters
15
+ ``pending_payment`` and is revoked when the checkout is cancelled. It is
16
+ never exposed in a URL. Callers that fail the policy receive a
17
+ non-disclosing 404, identical to a nonexistent submission.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import secrets
23
+ import time
24
+
25
+ from django.conf import settings
26
+
27
+ SESSION_KEY = "payment_checkouts"
28
+ DEFAULT_CHECKOUT_TTL_SECONDS = 3600 # 1 hour
29
+
30
+
31
+ def _checkout_ttl() -> int:
32
+ return int(
33
+ getattr(
34
+ settings,
35
+ "FORMS_WORKFLOWS_PAYMENT_CHECKOUT_TTL",
36
+ DEFAULT_CHECKOUT_TTL_SECONDS,
37
+ )
38
+ )
39
+
40
+
41
+ def grant_checkout_access(request, submission) -> None:
42
+ """Bind a short-lived checkout credential for *submission* to the session.
43
+
44
+ Called by the submit views right after a submission enters
45
+ ``pending_payment`` so the same browser session can complete (confirm,
46
+ return, or cancel) the checkout that belongs to it.
47
+ """
48
+ checkouts = dict(request.session.get(SESSION_KEY, {}))
49
+ checkouts[str(submission.id)] = {
50
+ "token": secrets.token_urlsafe(32),
51
+ "exp": time.time() + _checkout_ttl(),
52
+ }
53
+ request.session[SESSION_KEY] = checkouts
54
+ request.session.modified = True
55
+
56
+
57
+ def revoke_checkout_access(request, submission) -> None:
58
+ """Drop any checkout credential for *submission* from the session."""
59
+ checkouts = dict(request.session.get(SESSION_KEY, {}))
60
+ if checkouts.pop(str(submission.id), None) is not None:
61
+ request.session[SESSION_KEY] = checkouts
62
+ request.session.modified = True
63
+
64
+
65
+ def has_checkout_access(request, submission) -> bool:
66
+ """The single payment access policy used by every payment endpoint.
67
+
68
+ * Authenticated submitter: may act only on their own submission.
69
+ * Anonymous submission (``submitter`` is null): the caller's session
70
+ must hold an unexpired checkout credential issued by
71
+ :func:`grant_checkout_access`.
72
+ """
73
+ if submission.submitter_id is not None:
74
+ return (
75
+ request.user.is_authenticated and request.user.pk == submission.submitter_id
76
+ )
77
+
78
+ entry = request.session.get(SESSION_KEY, {}).get(str(submission.id))
79
+ if not entry:
80
+ return False
81
+ try:
82
+ return float(entry.get("exp", 0)) > time.time()
83
+ except (TypeError, ValueError):
84
+ return False
@@ -9,19 +9,27 @@ Configuration via standard Django settings::
9
9
 
10
10
  STRIPE_SECRET_KEY = "sk_test_..."
11
11
  STRIPE_PUBLISHABLE_KEY = "pk_test_..."
12
- STRIPE_WEBHOOK_SECRET = "whsec_..." # optional, for webhook verification
12
+ STRIPE_WEBHOOK_SECRET = "whsec_..." # REQUIRED to accept webhooks
13
+
14
+ Webhooks fail closed: when ``STRIPE_WEBHOOK_SECRET`` is absent or the
15
+ signature does not verify, the event is rejected and no state is applied.
13
16
  """
14
17
 
15
18
  from __future__ import annotations
16
19
 
17
- import json
18
20
  import logging
19
21
  from decimal import Decimal
20
22
  from typing import Any
21
23
 
22
24
  from django.conf import settings
23
25
 
24
- from .base import PaymentFlow, PaymentProvider, PaymentResult, PaymentStatus
26
+ from .base import (
27
+ PaymentFlow,
28
+ PaymentProvider,
29
+ PaymentResult,
30
+ PaymentStatus,
31
+ WebhookVerificationError,
32
+ )
25
33
 
26
34
  logger = logging.getLogger(__name__)
27
35
 
@@ -59,12 +67,20 @@ class StripePaymentProvider(PaymentProvider):
59
67
  submission_id: int,
60
68
  description: str = "",
61
69
  metadata: dict[str, Any] | None = None,
70
+ idempotency_key: str | None = None,
62
71
  ) -> PaymentResult:
63
72
  stripe = _get_stripe()
64
73
  stripe.api_key = settings.STRIPE_SECRET_KEY
65
74
 
66
75
  amount_cents = int(amount * 100)
67
76
 
77
+ # Forward the stable per-attempt idempotency key so a retried
78
+ # initiation of the same PaymentRecord cannot create duplicate
79
+ # PaymentIntents on Stripe's side.
80
+ request_options: dict[str, Any] = {}
81
+ if idempotency_key:
82
+ request_options["idempotency_key"] = idempotency_key
83
+
68
84
  try:
69
85
  intent = stripe.PaymentIntent.create(
70
86
  amount=amount_cents,
@@ -72,6 +88,7 @@ class StripePaymentProvider(PaymentProvider):
72
88
  description=description or f"Form submission #{submission_id}",
73
89
  metadata=metadata or {},
74
90
  automatic_payment_methods={"enabled": True},
91
+ **request_options,
75
92
  )
76
93
  return PaymentResult(
77
94
  success=True,
@@ -131,19 +148,27 @@ class StripePaymentProvider(PaymentProvider):
131
148
  stripe = _get_stripe()
132
149
  webhook_secret = getattr(settings, "STRIPE_WEBHOOK_SECRET", "")
133
150
 
151
+ if not webhook_secret:
152
+ # Fail closed: without the webhook secret we cannot prove the
153
+ # event came from Stripe, so no state change may be applied.
154
+ logger.error(
155
+ "Stripe webhook received but STRIPE_WEBHOOK_SECRET is not "
156
+ "configured; refusing to process the event."
157
+ )
158
+ raise WebhookVerificationError(
159
+ "STRIPE_WEBHOOK_SECRET is not configured; cannot verify event."
160
+ )
161
+
134
162
  payload = request.body
135
163
  sig_header = request.META.get("HTTP_STRIPE_SIGNATURE", "")
136
164
 
137
- if webhook_secret:
138
- try:
139
- event = stripe.Webhook.construct_event(
140
- payload, sig_header, webhook_secret
141
- )
142
- except (ValueError, stripe.error.SignatureVerificationError) as e:
143
- logger.warning("Stripe webhook verification failed: %s", e)
144
- return None
145
- else:
146
- event = stripe.Event.construct_from(json.loads(payload), stripe.api_key)
165
+ try:
166
+ event = stripe.Webhook.construct_event(payload, sig_header, webhook_secret)
167
+ except (ValueError, stripe.error.SignatureVerificationError) as e:
168
+ logger.warning("Stripe webhook signature verification failed: %s", e)
169
+ raise WebhookVerificationError(
170
+ "Stripe webhook signature verification failed."
171
+ ) from e
147
172
 
148
173
  if event.type == "payment_intent.succeeded":
149
174
  intent = event.data.object