Zippy Form is a Django package that simplifies the creation of dynamic forms without requiring any coding.
Setup
To integrate the Zippy Form package into your Django project, follow these steps:
1. Add Package Name
- Open your Django project's settings.py file.
- Add the package name
zippy_form
to your project's INSTALLED_APPS.
2. Migrate
- Open your terminal or command prompt.
- Run the following command to apply the migrations:
python manage.py migrate
3. Update URL Configuration
- In your Django project's urls.py file, add the following URL path to include the Zippy Form package's URLs:
path('form/', include('zippy_form.urls'))
By following these setup instructions, you'll successfully integrate the Zippy Form package into your Django project, allowing you to create dynamic forms with ease.
API Documentation
Click Here to view API Documentation.
Notes To Developer
# Subscribing to Zippy Form Events Using Callback Function
When specific functionalities occur on the Zippy Form, events will be triggered and you can subscribe to these events to receive data in your Django application.
Below is a list of events that you can subscribe to:
- New Account Created: This event is triggered when a new account is successfully created.
To subscribe to these event and receive data in your Django app, follow the below steps
- Open your project's settings file (usually named
settings.py
). - Import the function that you want to use for subscribing the event.
For example:
from yourapp.event_subscriptions import zippyform_after_account_create
Replace "yourapp.event_subscriptions" with the actual path to your event_subscription function. - Add the below snippet to the settings file,
ZF_EVENT_AFTER_ACCOUNT_CREATE = zippyform_after_account_create
Replace "zippyform_after_account_created" with the name of the function you imported.
- New Form Created: This event is triggered when a new form is successfully created.
To subscribe to these event and receive data in your Django app, follow the below steps
- Open your project's settings file (usually named
settings.py
). - Import the function that you want to use for subscribing the event. For example:
from yourapp.event_subscriptions import zippyform_after_form_create
Replace "yourapp.event_subscriptions" with the actual path to your event_subscription function. - Add the below snippet to the settings file,
ZF_EVENT_AFTER_FORM_CREATE = zippyform_after_form_create
- After Form Submit: This event is triggered when a form submitted(save or update).
To subscribe to these event and receive data in your Django app, follow the below steps
- Open your project's settings file (usually named
settings.py
). - Import the function that you want to use for subscribing the event. For example:
from yourapp.event_subscriptions import zippyform_after_form_submit
Replace "yourapp.event_subscriptions" with the actual path to your event_subscription function. - Add the below snippet to the settings file,
ZF_EVENT_AFTER_FORM_SUBMIT = zippyform_after_form_submit
# Subscribing to Zippy Form Events Using Webhooks
1. Enabling Webhooks for Zippy Form:
To enable webhooks for your project,
- Modify your .env file:
- Open your Django project's
.env
file. If it doesn't exist, create one in your project's root directory. - Add the following environment variables to enable webhooks:
ZF_ENABLE_WEBHOOK=True
ZF_WEBHOOK_BROKER_URL=amqp://localhost
ZF_WEBHOOK_BACKEND=rpc://
- Install the python-dotenv Package:
Ensure that you have the
python-dotenv
package installed in your Django application. If it's not installed, you can install it using pip:
pip install python-dotenv
- Import and load .env variables in your settings.py file:
In your Django project's
settings.py
file (usually found in the project's root directory), import and load the environment variables from the .env
file.
from dotenv import load_dotenv
import os
load_dotenv()
ZF_ENABLE_WEBHOOK = str(os.getenv('ZF_ENABLE_WEBHOOK'))
ZF_WEBHOOK_BROKER_URL = str(os.getenv('ZF_WEBHOOK_BROKER_URL'))
ZF_WEBHOOK_BACKEND = str(os.getenv('ZF_WEBHOOK_BACKEND'))
- ZF_ENABLE_WEBHOOK: Set this to
True
to enable webhooks for Zippy Form. - ZF_WEBHOOK_BROKER_URL: Add the broker URL for Celery (e.g., RabbitMQ URL).
- ZF_WEBHOOK_BACKEND: Configure the webhook backend (e.g., RabbitMQ).
2. Starting a Celery Worker for Zippy Form:
You have the flexibility to choose between two approaches for starting the Celery worker. Please follow one of the methods below:
Approach 1 (Preferred for Docker Setup):
- Auto-generate the Celery setup file at the project's root folder by running the following command:
python manage.py celery_init
- Once the "celery_init.py" file is created, you can start the Celery worker by running the file using the following command:
python3 celery_init.py
Approach 2:
- Create a Bash file (celery.sh) in the root folder of your Django project.
- Add the following contents to the celery.sh file:
export DJANGO_SETTINGS_MODULE='your_project_name.settings'
celery -A zippy_form worker -l info --loglevel=info --logfile=celery.log -D
Set your_project_name
to the actual name of your Django project.
- Once the
celery.sh
file is created, run the following command within your virtual environment from the Django project's root folder:
source celery.sh
This command will start the Celery worker process for Zippy Form in the background.
Celery logs will be saved at the Django project's root folder with the name celery.log
.
Below is a list of events that you can subscribe to:
- New Form Created: This event is triggered when a new form is successfully created.
- After Form Submit: This event is triggered when a form submitted(save or update).
# Zippy Form API Hooks
1. API: Dynamic Form / Create Form:
To handle a specific logic before form created(eg. restrict form creation based on user subscription plan),follow the below steps
- Open your project's settings file (usually named
settings.py
). - Add the below snippet to the settings file,
ZF_API_BEFORE_CREATE_FORM = from yourapp.subscriptions import zippyform_before_create_form
Replace "zippyform_before_create_form" with the name of the function you imported.
To return an error when creating a form, the hook should return this response:
def zippyform_before_create_form(account_id):
response = {}
response['return_response'] = True
response['status'] = "error"
response['status_code'] = 400
response['msg'] = "Error Message"
return response
To skip return an error when creating a form, the hook should return this response:
def zippyform_before_create_form(account_id):
response = {}
response['return_response'] = False
return response
2. API: Form Builder / List All Forms With Pagination:
To handle a specific logic before response returned(eg. restrict form response based on user subscription plan),follow the below steps
- Open your project's settings file (usually named
settings.py
). - Add the below snippet to the settings file,
ZF_API_LIST_ALL_FORMS_FORMAT_SUCCESS_RESPONSE = from yourapp.subscriptions import zippyform_format_form_response
Replace "zippyform_format_form_response" with the name of the function you imported.
To include additional data on the form list response:
def zippyform_format_form_response(response_data, form_id, account_id):
response_data['can_access_form'] = True
return response_data
3. API: Form Builder / List All Form Fields:
To handle a specific logic before response returned(eg. restrict form field response based on user subscription plan),follow the below steps
- Open your project's settings file (usually named
settings.py
). - Add the below snippet to the settings file,
ZF_API_FORM_BUILDER_LIST_ALL_FORM_FIELDS_BEFORE_RESPONSE = from yourapp.subscriptions import zippyform_form_builder_before_form_fields_response
Replace "zippyform_form_builder_before_form_fields_response" with the name of the function you imported.
To return an error when listing form fields, the hook should return this response:
def zippyform_form_builder_before_form_fields_response(current_frontend_url, account_id):
response = {}
response['return_response'] = True
response['status'] = "error"
response['status_code'] = 400
response['msg'] = "Error Message"
return response
To skip return an error when listing form fields, the hook should return this response:
def zippyform_form_builder_before_form_fields_response(current_frontend_url, account_id):
response = {}
response['return_response'] = False
return response
4. API: Form Builder / List All Form Fields:
To format the API response(eg. add/remove additional data to form field response),follow the below steps
- Open your project's settings file (usually named
settings.py
). - Add the below snippet to the settings file,
ZF_API_FORM_BUILDER_LIST_ALL_FORM_FIELDS_FORMAT_SUCCESS_RESPONSE = from yourapp.subscriptions import zippyform_form_builder_format_form_fields_response
Replace "zippyform_form_builder_format_form_fields_response" with the name of the function you imported.
To include additional data on the form field list response:
def zippyform_form_builder_format_form_fields_response(response_data, form_id, account_id):
response_data['can_access_builder'] = True
return response_data
5. API: Dynamic Form / Submit Form:
To handle a specific logic before form submit(eg. restrict submitting form based on user subscription plan),follow the below steps
- Open your project's settings file (usually named
settings.py
). - Add the below snippet to the settings file,
ZF_API_BEFORE_SUBMIT_FORM = from yourapp.subscriptions import zippyform_before_form_submit
Replace "zippyform_before_form_submit" with the name of the function you imported.
Note: This hook will be called before the form fields validated
To return an error when submitting form, the hook should return this response:
def zippyform_before_form_submit(form_id, account_id):
response = {}
response['return_response'] = True
response['status'] = "error"
response['status_code'] = 400
response['msg'] = "Error Message"
return response
To skip return an error when submitting form, the hook should return this response:
def zippyform_before_form_submit(form_id, account_id):
response = {}
response['return_response'] = False
return response
6. API: Dynamic Form / List All Form Fields:
To format the API response(eg. add/remove additional data to form field response),follow the below steps
- Open your project's settings file (usually named
settings.py
). - Add the below snippet to the settings file,
ZF_API_DYNAMIC_FORM_LIST_ALL_FORM_FIELDS_FORMAT_SUCCESS_RESPONSE = from yourapp.subscriptions import zippyform_dynamic_form_format_fields_response
Replace "zippyform_dynamic_form_format_fields_response" with the name of the function you imported.
To include additional data on the form field list response:
def zippyform_dynamic_form_format_fields_response(response_data, form_id, account_id):
response_data['can_access_form'] = True
return response_data
# Zippy Form Permission
To add custom permission to the API response(eg. check user status),follow the below steps
- Open your project's settings file (usually named
settings.py
). - Add the below snippet to the settings file,
ZF_PERMISSION = from yourapp.permissions import zippyform_custom_permission
Replace "zippyform_custom_permission" with the name of the function you imported.
def zippyform_custom_permission(account_id):
response = {}
if error:
response['has_error'] = True
response['status'] = "error"
response['status_code'] = 400
response['msg'] = "Error"
else:
response['has_error'] = False
return response
# Overwrite Zippy Form Defaults
-
Application Type: By default, Zippy Form is set to operate in Standalone
mode. However, to configure Zippy Form to support SaaS applications (Multi-Tenant Applications), include the following line in your project settings file:
ZF_APPLICATION_TYPE = 'saas'
-
List Per Page Size: To overwrite the default per page size of list (which is set to 6), you can configure it in the project settings file by adding the following line:
ZF_LIST_PER_PAGE = 10
Replace "10" with your desired page size value. This allows you to customize the default per page size for the Zippy Form list.
-
Form Field Label Unique: If you need to allow duplicate values for form field labels, you can configure it in the project settings file by adding the following line:
ZF_IS_FIELD_LABEL_UNIQUE = False
FAQ
# Q1: Does Zippy Form support multi-step forms?
A: Yes, Zippy Form does support multi-step forms, allowing you to create and manage forms with multiple steps to collect information in a structured and user-friendly manner.
# Q2: Can a form be created without an account?
A: No, an account needs to be created before a form can be created because each form needs to be mapped to an account.
# Q3: How many accounts can be created?
A: There is no limit to creating accounts. If you are using the package in a standalone application, you can create a single account and map all the forms to it. If you are using the package in a multi-tenant application, you can create an account for each tenant and map the forms under that respective account.
# Q4: How can I synchronize my application's user account (tenant) with Zippy Form?
A: When creating an account in Zippy Form, include the unique ID associated with your application's user account(tenant) in the meta_detail
parameter. This unique ID serves as a linkage between your application and Zippy Form.
After you've passed the unique ID, you can retrieve it when Zippy Form events occur. Whether you're using event callback functions or webhooks, the meta_detail
data will be included in the event payload.
# Q5: Can I include additional details when creating a form in Zippy Form?
A: Yes, you can pass additional details when creating a form in Zippy Form using the meta_detail
parameter. This allows you to provide extra information or context related to the form. Subsequently, when Zippy Form events occur, whether you're utilizing event callback functions or webhooks, the meta_detail
data you passed will be included in the event payload, enabling you to access and use it as needed.
# Q6: Why are form field labels set as unique?
A: Form field labels are set as unique by default to serve as unique identifiers when syncing form submission data to a Google Sheets, ensuring accurate data organization and management. If you wish to override this default behavior, please refer to the Overwrite Zippy Form Defaults > Form Field Label Unique
section for instructions.
# Q7: What does receiving a "permission_error" with a 403 code mean when interacting with an API?
A: If you encounter a "permission_error" with a 403 status code, it typically indicates that there is an issue with authentication. This error commonly occurs when you have either missed passing the ZF-SECRET-KEY
token in the header of your API request or the ZF-SECRET-KEY
token provided is invalid. To resolve this, ensure that you include the correct and valid ZF-SECRET-KEY
token in your API request's header for proper authentication.
# Q8: Where can I obtain the "ZF-SECRET-KEY"?
A: You can use your Zippy Form account ID as the ZF-SECRET-KEY
. This ID uniquely identifies your Zippy Form account. Obtain your Zippy Form account ID by making an API request to the Form Builder / List All Accounts
endpoint. This API call will provide you with the necessary account ID to use as the ZF-SECRET-KEY
in your integration.
# Q9: Why am I receiving an "Invalid Form ID" error message when attempting to submit a form?
A: You will encounter the "Invalid Form ID" error message when submitting a form if the following conditions are not met:
- Form Status: The form can only be submitted if its status is
active
. If the form is in any other state such as deleted
, inactive
, or draft
you will receive this error message. - Form ID Validity: Ensure that the form ID you have provided is accurate and corresponds to an existing, active form. An incorrect or nonexistent form ID will trigger the "Invalid Form ID" error.
# Q10: Why are the fields I created not displaying on the form?
A: Only active fields will be displayed on the form for submission. To resolve this, you can use the Form Builder / Update Form
API to update the field status from draft
to active
ensuring that they are visible and can be submitted on the form.
# Q11: Can I handle my own business logic when an account is created, a form is created, or a form is submitted in Zippy Form?
A: Yes, you have the flexibility to handle your own business logic when working with Zippy Form. The approach depends on whether you've installed the Zippy Form package in a standalone application or a multi-tenant application:
- Standalone Application: If you've integrated Zippy Form into a standalone application, you can subscribe to Zippy Form events using callback functions. This feature enables you to access event data within any function you add to your application.
- Multi-Tenant Application: For multi-tenant applications, you can subscribe to Zippy Form events using webhooks. Webhooks provide a way to receive real-time notifications about Zippy Form events, and you can handle these events in your application according to your business needs.
For detailed instructions on how to implement these approaches, please refer to the "Notes To Developer" section in the Zippy Form documentation, which provides comprehensive information on how to use callback functions and webhooks for event handling.
# Q12: Does the "After Form Submit" event trigger after each form step or only at the final step?
A: The "After Form Submit" event typically triggers after the last form step has been completed and the entire form has been submitted. This event occurs once the user has finished providing input and confirmed their submission. It does not trigger after each form step but rather at the conclusion of the entire form submission process.
# Q13: Does Zippy Form support unique validation?
A: Yes, Zippy Form supports unique validation. Unique validation checks submitted entries in forms with draft
and active
statuses to ensure that values are unique, preventing duplicate entries.
# Q14: Can I delete an uploaded file?
A: Yes, you can delete an uploaded file. If the field is set as not required, you can simply pass "reset" to the field key when updating the entry, and it will delete the previously uploaded file.
# Q15: How can I update a form without adding a new file and retaining the old file?
A: To update a form without adding a new file and retain the old file, you can pass an empty value to the field key. This action will skip the required validation during the update and keep the old file intact.
# Q16: Why is my media not saving to the media folder in Zippy Form?
A: Zippy Form does not control the location where media is saved; instead, it relies on your Django application's configuration. To ensure your media is saved to the correct folder, follow these steps:
1. Configure Django Settings:
Open your Django project's settings file and add the following snippets:
import os
API_DOMAIN = "http://127.0.0.1:8000"
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_FOLDER = '/media/'
MEDIA_URL = API_DOMAIN + MEDIA_FOLDER
ZF_API_BASE_URL = API_DOMAIN + "/api/v1/form"
2. Update URL Configuration:
In your Django project's urls.py file, include the ZippyForm package's URL patterns, and make sure to handle static media files by adding the following:
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
... ,
path('form/', include('zippy_form.urls')), # Include Zippy Form package's URL patterns here
] + static(settings.MEDIA_FOLDER, document_root=settings.MEDIA_ROOT)
By following these steps and configuring your Django project correctly, media files should be saved to the specified media folder.
# Q17: How can I make my submitted form data sync to Google Sheet?
A: To sync the submitted form data to Google Sheet, follow these steps:
- Open your project's settings.py file.
- Add the following snippet to enable the syncing of form submissions to Google Sheet:
ZF_GSHEET_SYNC_FORM_SUBMISSION = True
- Additionally, you need to provide the Google Sheet credentials. Add your credentials to the Zippy Form project settings by assigning them to
ZF_GSHEET_CREDENTIALS
.
Sample Gsheet Credentials:
ZF_GSHEET_CREDENTIALS = {
"type": "service_account",
"project_id": "gsheet-test-******",
"private_key_id": "",
"private_key": "",
"client_email": "",
"client_id": "",
"auth_uri": "",
"token_uri": "",
"auth_provider_x509_cert_url": "",
"client_x509_cert_url": "",
"universe_domain": ""
}
By following these steps, your form submissions will be automatically synchronized with Google Sheet. This ensures that your data is efficiently organized and accessible for further analysis or processing.
# Q18: Where can I find the Google Sheet URL to view the data submitted through the form?
A: After enabling Google Sheet form submission sync and adding Google Sheet credentials:
- Create a new form.
- Once the form is created, Google Sheet will automatically be created and shared with the admin email provided in your account. You can also find the Google Sheet URL on the
Form Builder / List All Forms
API.
# Q19: Why is the Google Sheet document created blank after creating a new form?
A: The Google Sheet document remains blank initially because the headers for the sheet are synced only when triggering the Form Builder / Update Form
API. This synchronization ensures that only the active fields from the form are transferred to the Google Sheet as headers, allowing for precise and organized data tracking.
# Q20: Can I move/rearrange columns in Google Sheet to different positions directly within the sheet?
A: Yes, you can freely move columns to different positions within the Google Sheet. However, it's essential to ensure that you do not modify the column headings while moving them, as altering column headings can lead to synchronization issues with associated data.
# Q21: When running celery.sh(source celery.sh) in the virtual environment, I receive the warning "A node named celery@pc-ThinkPad-E14-Gen-3 is already using this process mailbox." How can I fix this issue?
A: The warning message "A node named celery@pc-ThinkPad-E14-Gen-3 is already using this process mailbox!" indicates that you have multiple Celery workers running with the same node name, and there's an attempt to use the same process mailbox for these workers. In Celery, each worker should have a unique node name to avoid conflicts.
The node name typically consists of the word "celery" followed by a hostname or some identifier that distinguishes it from other workers. In your case, the node name is "celery@pc-ThinkPad-E14-Gen-3," which is derived from your computer's hostname.
To resolve this issue:
- FKill All Running Celery Workers: Run the following command to terminate all running Celery workers:
pkill -f "celery"
This will stop all Celery workers gracefully and prevent conflicts.
- Restart Celery Worker process for Zippy Form: To restart the Celery Worker Process for Zippy Form,follow the steps provided in the
Subscribing to Zippy Form Events Using Webhooks
section, specifically in the Starting a Celery Worker for Zippy Form
subsection.
By following these steps, Celery will work properly in your environment.
# Q22: How many webhooks can be added to an account?
A: There is no limit for adding webhooks to an account. You can add webhooks to an account using the Form Builder / Create Webhook
API.
# Q23: In what timezone will date and timestamps be displayed?
A: Date and timestamp will be based on the timezone set for the account.
# Q24: What payment gateways are supported in Zippy Form?
A: As of now, Zippy Form exclusively supports the Stripe Payment Gateway. We're continuously evaluating and working on integrating additional payment gateways to offer more options in the future.
# Q25: What credentials are required to integrate the Stripe Payment Gateway into Zippy Form?
A: To integrate the Stripe Payment Gateway into Zippy Form, both test and live secret keys, public keys are necessary. Regardless of whether your application is Standalone or SaaS (Multi-Tenant), follow these steps:
- Store your Stripe test and live secret keys, public keys securely in a
.env
file. - Access and utilize these keys within the project
settings.py
file using the following configurations:
# For Development Environment
ZF_PAYMENT_GATEWAY_STRIPE_SECRET_KEY_DEV = "your_test_secret_key_here"
ZF_PAYMENT_GATEWAY_STRIPE_PUBLIC_KEY_DEV = "your_test_public_key_here"
# For Live/Production Environment
ZF_PAYMENT_GATEWAY_STRIPE_SECRET_KEY_LIVE = "your_live_secret_key_here"
ZF_PAYMENT_GATEWAY_STRIPE_PUBLIC_KEY_LIVE = "your_live_public_key_here"
Ensure to replace "your_test_secret_key_here" and "your_live_secret_key_here" with your respective Stripe test and live secret keys & "your_test_public_key_here" and "your_live_public_key_here" with your respective Stripe test and live public keys. This setup ensures secure and efficient payment processing within Zippy Form, regardless of your application type.
If your application is SaaS (Multi-Tenant), follow these steps:
- Store your Stripe Connect URL securely in a
.env
file. - Access and utilize these keys within the project
settings.py
file using the following configurations:
# For Development Environment
ZF_PAYMENT_GATEWAY_STRIPE_CONNECT_URL_DEV = "your_test_stripe_connect_url_here"
# For Live/Production Environment
ZF_PAYMENT_GATEWAY_STRIPE_CONNECT_URL_LIVE = "your_live_stripe_connect_url_here"
Ensure to replace "your_test_stripe_connect_url_here" and "your_live_stripe_connect_url_here" with your respective Stripe test and live Connect URL.
# Q26: How can I configure the application fee percentage to collect when a tenant receives payment from an end user using Zippy Form in a SaaS application?
A: To set the application fee percentage for Stripe within Zippy Form's SaaS application, follow the below steps
- Open your project's settings file (usually named
settings.py
). - Add the below snippet to the settings file,
ZF_PAYMENT_GATEWAY_STRIPE_APPLICATION_FEE_PERCENTAGE = from yourapp.subscriptions import stripe_application_fee_percentage
Replace "stripe_application_fee_percentage" with the name of the function you imported.
Note: Function imported here need to return a positive integer value
# Q27: Why isn't the payment form collecting payments?
A: If your payment form isn't collecting payments, ensure you've completed these steps:
- Enable Payment: Ensure that the payment feature is activated for the form.
- Set Primary Payment Gateway: Set the primary payment gateway within your account settings.
- Configuration Check: Verify that the payment gateway keys are correctly configured in your project settings.py file.
Ensuring payment functionality is enabled, setting a primary payment gateway, and correctly configuring the payment gateway keys within the project settings are essential to facilitate successful payment collection through the form.
# Q28: What is the difference between fixed and dynamic price in the payment form settings?
A: In the context of payment form settings:
- Fixed Price: This option is suitable when there's a pre-defined, unchanging amount that needs to be collected upon form submission. For instance, it's commonly used in scenarios like application forms, where the payment amount remains constant regardless of user input.
- Dynamic Price: Select this option when you want users to input the amount they wish to pay upon form submission. In this case, users have the flexibility to enter the payment amount themselves, such as in donation forms or scenarios where the payment amount can vary based on user discretion. The submitted form collects the specified amount provided by the user.
# Q29: How can I incorporate my custom form URL into the Custom Form QR code?
A: To incorporate your custom form URL into the Custom Form QR code,
- Modify your .env file:
- Open your Django project's
.env
file. If it doesn't exist, create one in your project's root directory. - Add the following environment variables to enable webhooks:
ZF_CUSTOM_FORM_FRONTEND_URL='www.domain.com/form'
ZF_CUSTOM_FORM_NON_ADMIN_FRONTEND_URL='www.domain.com/custom_form'
- Import and load .env variables in your settings.py file:
In your Django project's
settings.py
file (usually found in the project's root directory), import and load the environment variables from the .env
file.
from dotenv import load_dotenv
import os
load_dotenv()
ZF_CUSTOM_FORM_FRONTEND_URL = str(os.getenv('ZF_CUSTOM_FORM_FRONTEND_URL'))
ZF_CUSTOM_FORM_NON_ADMIN_FRONTEND_URL = str(os.getenv('ZF_CUSTOM_FORM_NON_ADMIN_FRONTEND_URL'))
Releases
Version | Features |
---|
1.0.0 | Form Builder, Dynamic Form, Subscribing To Events Using Callback Function |
1.1.0 | Sync Submitted Form Entries To Google Sheet |
1.2.0 | Webhook Support |
1.3.0 | Payment Form |