Skip to content

Commit

Permalink
feat(form): Add stripe component.
Browse files Browse the repository at this point in the history
  • Loading branch information
vinci1it2000 committed Feb 12, 2024
1 parent 7e83118 commit e382397
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 15 deletions.
5 changes: 3 additions & 2 deletions schedula/utils/form/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,8 +312,9 @@ def add2csrf_protected(self, app=None, item=None):
else:
if app.secret_key is None:
app.secret_key = secrets.token_hex(32)
for endpoint in app.view_functions:
self._csrf_protected.add(('view', endpoint))
for endpoint, func in app.view_functions.items():
if not getattr(func, 'csrf_exempt', False):
self._csrf_protected.add(('view', endpoint))
return app

def app(self, root_path=None, depth=1, mute=False, blueprint_name=None,
Expand Down
11 changes: 11 additions & 0 deletions schedula/utils/form/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,14 @@ class Config:
# many DBaaS options automatically close idle connections.
SQLALCHEMY_ENGINE_OPTIONS = {"pool_pre_ping": True}
SQLALCHEMY_TRACK_MODIFICATIONS = False

STRIPE_SECRET_KEY = os.environ.get(
"STRIPE_SECRET_KEY", 'sk_test_EP9uAZ5ZF1yz1LYTXlCWbRh0'
)
STRIPE_PUBLISHABLE_KEY = os.environ.get(
"STRIPE_PUBLISHABLE_KEY", 'pk_test_5IqeFypqBcFlrqZ7KWGWA21H'
)
STRIPE_WEBHOOK_SECRET_KEY = os.environ.get(
"STRIPE_WEBHOOK_SECRET_KEY",
'whsec_a611c4ad09e38ff44444e13635ba01f6a62acbb2f566fb1c945560e9e46e3556'
)
2 changes: 1 addition & 1 deletion schedula/utils/form/react/deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ rm -r ../static/schedula/css
mkdir "../static/schedula/css"
rm -r ../static/schedula/media
mkdir "../static/schedula/media"

sudo n stable
echo "Installing dependencies..."
npm i --force
echo "Bundle index..."
Expand Down
2 changes: 2 additions & 0 deletions schedula/utils/form/react/src/core/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const Domain = React.lazy(() => import('./Domain'));
const Element = React.lazy(() => import('./Element'));
const FlexLayout = React.lazy(() => import('./FlexLayout'));
const Static = React.lazy(() => import('./Static'));
const Stripe = React.lazy(() => import('./Stripe'));
const Title = React.lazy(() => import('./Title'));


Expand All @@ -18,6 +19,7 @@ export function generateComponents() {
Element,
FlexLayout,
Static,
Stripe,
Title
}
}
Expand Down
114 changes: 104 additions & 10 deletions schedula/utils/form/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
"""
It provides functions to build the base form flask app.
"""
import logging
import secrets
import datetime
import collections
import os.path as osp
import schedula as sh
from .mail import Mail
Expand All @@ -33,11 +35,14 @@
Security, SQLAlchemyUserDatastore, current_user as cu, auth_required
)

log = logging.getLogger(__name__)


def default_get_form_context():
return {
'userInfo': getattr(cu, "get_security_payload", lambda: {})(),
'reCAPTCHA': current_app.config.get('RECAPTCHA_PUBLIC_KEY')
'reCAPTCHA': current_app.config.get('RECAPTCHA_PUBLIC_KEY'),
'stripeKey': current_app.config.get('STRIPE_PUBLISHABLE_KEY')
}


Expand All @@ -46,15 +51,6 @@ def basic_app(sitemap, app):
if getattr(sitemap, 'basic_app_config'):
app.config.from_object(sitemap.basic_app_config)

@app.after_request
def add_security_headers(resp):
from flask import session
nonce = session.get('nonce')
if not nonce:
session['nonce'] = nonce = secrets.token_urlsafe(16)
resp.headers['Content-Security-Policy'] = f"script-src 'nonce-{nonce}'"
return resp

# Create database connection object
db = SQLAlchemy(app)

Expand Down Expand Up @@ -165,6 +161,7 @@ class User(db.Model, fsqla.FsUserMixin):

def get_security_payload(self):
return {k: v for k, v in {
'id': self.id,
'email': self.email,
'username': self.username,
'firstname': self.firstname,
Expand Down Expand Up @@ -264,4 +261,101 @@ def get_locale():

Babel(app, locale_selector=get_locale)
mail = Mail(app)

@app.route('/stripe/create-checkout-session', methods=['POST'])
def create_payment():
import stripe
try:
data = request.get_json() if request.is_json else dict(request.form)
if not isinstance(data, list):
data = data,
lookup_keys = collections.OrderedDict()
api_key = current_app.config.get('STRIPE_SECRET_KEY')
for i, d in enumerate(data):
if 'lookup_key' in d:
sh.get_nested_dicts(
lookup_keys, d['lookup_key'], default=list
).append(i)
if lookup_keys:
for price, it in zip(stripe.Price.list(
api_key=api_key,
lookup_keys=list(lookup_keys.keys()),
expand=['data.product']
).data, lookup_keys.values()):
for i in it:
data[i].update({'price': price.id})
session = stripe.checkout.Session.create(
api_key=api_key,
ui_mode='embedded',
line_items=data,
mode='payment',
automatic_tax={'enabled': True},
redirect_on_completion='never',
metadata={
# 'customer': f'{cu.id} - {cu.firstname} {cu.lastname}'
}
)
except Exception as e:
return jsonify(error=str(e))

return jsonify(
clientSecret=session.client_secret, sessionId=session.id
)

@app.route('/stripe/session-status', methods=['GET'])
def session_status():
import stripe
session = stripe.checkout.Session.retrieve(
request.args.get('session_id'),
api_key=current_app.config.get('STRIPE_SECRET_KEY')
)
session.customer_details.email
status = session.status
if status == "complete":
msg = 'Payment succeeded!'
category = 'success'
elif status == "processing":
msg = 'Your payment is processing.'
category = 'success'
elif status == "requires_payment_method":
msg = 'Your payment was not successful, please try again.'
category = 'success'
else:
msg = 'Something went wrong.'
category = 'success'
flash(str(lazy_gettext(msg)), category)
return jsonify(
status=status,
customer_email=session.customer_details.email,
userInfo=getattr(cu, "get_security_payload", lambda: {})()
)

@app.route('/stripe/webhook', methods=['POST'])
def stripe_webhook():
import stripe
payload = request.data
sig_header = request.headers['STRIPE_SIGNATURE']

try:
event = stripe.Webhook.construct_event(
payload, sig_header,
current_app.config.get('STRIPE_WEBHOOK_SECRET_KEY')
)
except ValueError as e:
# Invalid payload
raise e
except stripe.error.SignatureVerificationError as e:
# Invalid signature
raise e

# Handle the event
if event['type'] == 'payment_intent.succeeded':
payment_intent = event['data']['object']
# ... handle other event types
else:
log.info('Unhandled event type {}'.format(event['type']))

return jsonify(success=True)

stripe_webhook.csrf_exempt = True
return app
3 changes: 1 addition & 2 deletions schedula/utils/form/templates/schedula/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,7 @@
formData,
editOnChange,
preSubmit,
postSubmit,
nonce: "{{ session['nonce'] | tojson | safe }}"
postSubmit
})
});
}
Expand Down

0 comments on commit e382397

Please sign in to comment.