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.
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/CHANGELOG.md +46 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/PKG-INFO +4 -2
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/payments/__init__.py +8 -1
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/payments/base.py +24 -0
- django_forms_workflows-0.77.5/django_forms_workflows/payments/checkout.py +84 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/payments/stripe_provider.py +38 -13
- django_forms_workflows-0.77.5/django_forms_workflows/payments/views.py +484 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/payment_collect.html +8 -2
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/payment_error.html +6 -3
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/views.py +10 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/pyproject.toml +5 -4
- django_forms_workflows-0.77.3/django_forms_workflows/payments/views.py +0 -358
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/LICENSE +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/README.md +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/__init__.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/admin.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/api_urls.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/api_views.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/apps.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/callback_registry.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/conditions.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/context_processors.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/data_sources/__init__.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/data_sources/base.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/data_sources/database_source.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/data_sources/ldap_source.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/data_sources/user_source.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/diff_views.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/email_backends/__init__.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/email_backends/gmail_api.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/form_builder_urls.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/form_builder_views.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/forms.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/handlers/__init__.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/handlers/api_handler.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/handlers/base.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/handlers/database_handler.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/handlers/email_handler.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/handlers/executor.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/handlers/file_handler.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/handlers/ldap_handler.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/history.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/ldap_backend.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/management/commands/create_default_templates.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/management/commands/pull_forms.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/management/commands/push_forms.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/management/commands/seed_farm_demo.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/management/commands/seed_pcn_form.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/management/commands/seed_prefill_sources.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/management/commands/sync_ldap_profiles.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/management/commands/sync_ldap_users.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/management/commands/test_db_connection.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0001_initial.py +0 -0
- {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
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0003_postsubmissionaction.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0004_formtemplate.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0005_advanced_conditional_logic.py +0 -0
- {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
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0007_add_userprofile_ldap_enhancements.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0008_add_file_workflow_models.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0009_add_formfield_readonly.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0010_add_prefillsource_template_fields.py +0 -0
- {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
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0012_add_approval_step_fields.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0013_add_database_query_key.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0014_add_form_category.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0015_add_workflowstage_staged_approvals.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0016_add_pdf_generation.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0017_add_notification_batching.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0018_add_form_category_parent.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0019_add_ldap_group_profile.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0020_add_bulk_export_field.py +0 -0
- {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
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0022_add_bulk_pdf_export.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0023_add_performance_indexes.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0024_add_allow_resubmit.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0025_workflowstage_approve_label.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0026_alter_formfield_width.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0027_formsubmission_form_data_gin.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0028_workflowstagegroupconfig.py +0 -0
- {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
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0030_formfield_phone_type.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0031_sub_workflows.py +0 -0
- {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
- {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
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0034_add_notificationlog.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0035_migrate_prefill_source_strings.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0036_add_workflowstage_auto_created.py +0 -0
- {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
- {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
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0039_update_help_text.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0040_migrate_deprecated_features.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0041_remove_deprecated_fields.py +0 -0
- {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
- {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
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0044_remove_subworkflowinstance_label.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0045_stageapprovalgroup_workflow_name_label.py +0 -0
- {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
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0047_add_hide_approval_history.py +0 -0
- {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
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0049_formfield_multifile_type.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0050_workflowdefinition_collapse_parallel_stages.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0051_formdefinition_is_listed.py +0 -0
- {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
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0053_stageformfieldnotification_static_emails.py +0 -0
- {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
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0055_add_send_back.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0056_calculated_formula_spreadsheet.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0057_add_currency_field_type.py +0 -0
- {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
- {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
- {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
- {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
- {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
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0063_formdefinition_api_enabled_apitoken.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0064_add_reviewer_groups.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0065_workflownotification.py +0 -0
- {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
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0067_migrate_legacy_notify_flags.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0068_drop_legacy_notify_fields.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0069_add_signature_field_type.py +0 -0
- {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
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0071_alter_pendingnotification_notification_type.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0072_add_change_history.py +0 -0
- {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
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0074_add_notification_rule.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0075_migrate_to_notification_rules.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0076_update_notification_event_choices.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0077_drop_legacy_notification_models.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0078_allow_anonymous_submissions.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0079_webhookendpoint_webhookdeliverylog.py +0 -0
- {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
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0081_add_success_page_fields.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0082_add_document_template_model.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0083_workflowdefinition_start_trigger.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0084_add_shared_option_list.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0085_add_payment_system.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0086_add_embed_enabled.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0087_notificationrule_use_triggering_stage.py +0 -0
- {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
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0089_workflowstage_hide_comment_field.py +0 -0
- {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
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0091_add_uuid_fields.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0092_add_user_notification_preference.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0093_normalize_conditional_operators.py +0 -0
- {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
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0095_notificationrule_cc_fields.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0096_notificationlog_cc_bcc_emails.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/0097_notificationlog_delivery_reconciliation.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/migrations/__init__.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/models.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/payments/registry.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/reporting_views.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/signals.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/sso_backends.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/sso_urls.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/sso_views.py +0 -0
- {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
- {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
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/static/django_forms_workflows/css/forms.css +0 -0
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/sync_api.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/sync_views.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/tasks.py +0 -0
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/_category_node.html +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/_field_value.html +0 -0
- {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
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/analytics_dashboard.html +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/api/docs.html +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/approval_inbox.html +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/approve.html +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/base.html +0 -0
- {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
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/completed_approvals.html +0 -0
- {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
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/embed_base.html +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/embed_success.html +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/form_categories.html +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/form_embed.html +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/form_list.html +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/form_submit.html +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/my_submissions.html +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/notification_preferences.html +0 -0
- {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
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/rate_limited.html +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/reassign_task.html +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/resubmit_confirm.html +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/sso/login.html +0 -0
- {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
- {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
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/submission_detail.html +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/submission_pdf.html +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/submission_success.html +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/django_forms_workflows/withdraw_confirm.html +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/emails/approval_notification.html +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/emails/approval_reminder.html +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/emails/approval_request.html +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/emails/email_styles.html +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/emails/escalation_notification.html +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/emails/notification_digest.html +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/emails/rejection_notification.html +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/emails/submission_notification.html +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templates/registration/login.html +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templatetags/__init__.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/templatetags/forms_workflows_tags.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/urls.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/utils.py +0 -0
- {django_forms_workflows-0.77.3 → django_forms_workflows-0.77.5}/django_forms_workflows/workflow_builder_views.py +0 -0
- {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
|
+
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 (>=
|
|
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
|
|
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_..." #
|
|
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
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|