利用Python的Flask框架來構(gòu)建一個簡單的數(shù)字商品支付解決方案
作為一個程序員,我有時候忘了自己所具有的能力。當事情沒有按照你想要的方式發(fā)展時,卻很容易忘記你有能力去改變它。昨天,我意識到,我已經(jīng)對我所出售的書的付款處理方式感到忍無可忍了。我的書完成后,我使用了三個不同的數(shù)字商品支付處理器,在對它們?nèi)齻€都感到不滿后,我用Python和Flask,兩個小時的時間寫出了我自己的解決方案。沒錯!兩個小時!現(xiàn)在,這個系統(tǒng)支撐著我的書籍付費流程,整個過程難以置信的簡單,你可以在20秒內(nèi)購買書籍并開始閱讀。
往下看,看我是如何在一夜之間完成我自己的數(shù)字商品支付解決方案的。
支付處理器的付款問題
在我開始賣書的時候,我綜合用了兩種支付服務(一種是信用卡,另一種是PayPal)。最終,我發(fā)現(xiàn)了一個可以支持兩者的處理方式。可是我對它們都不滿意。最常用的那個支付處理器,要求用戶在銷售商的系統(tǒng)中創(chuàng)建一個賬戶,并且輸入他們的郵箱地址(盡管郵箱并沒有用)。
另外,我嘗試使用Google Analytics 在全部的訪問中追蹤訪客,包括他們的結(jié)賬過程,這一過程也很艱辛。我經(jīng)常感覺到,如果我能夠讓它工作起來,并且能夠在我的書籍頁面進行A/B測試,我能極大地提高銷量。但是因為不能很好的追蹤,我就沒那么走運了。
最后,使用三個不同的支付處理器,發(fā)送書籍更新非常耗時。沒有一個能很好的支持更新,而我希望有個“一鍵”解決方案來發(fā)送我的書籍更新。我就沒找到一個類似的服務。
歐耶,我是個程序員
昨天,當我收到一個顧客的郵件,抱怨支付過程是如何的困難并且告訴我可能因此損失了很多銷量后,我忍無可忍了。我決定整一個自己的數(shù)字商品管理解決方案。我需要做到下面這樣了流程:
當客戶點擊“Buy Now”按鈕后,他們應該僅僅被要求輸入他們的email地址和信用卡信息。點擊“Confirm”后被帶到一個獨一無二的URL去下載書籍(專門為了這次交易而生成的)。一封包含這個URL的郵件應該被發(fā)送給客戶(防止用戶需要重新下載這本書)。對他們重復下載的次數(shù)應該有個限制(5次)。交易信息和客戶信息應該被存放在數(shù)據(jù)庫中,發(fā)送書籍更新應該只是一個命令的事。
顯然,這并不是那么的復雜。最復雜的部分是動態(tài)生成導向特定書籍版本的獨一無二的URL。其他的事情都挺簡單的。
“Flask前來救援”或是“一個100行代碼的數(shù)字商品支付解決方案”
劇透:程序的最終結(jié)果正好是100行代碼。對于這種規(guī)模的web應用,F(xiàn)lask是個很好的選擇。并不需要大量的模板(cough就像 Django cough),但是有很多很好的插件作為支持。Bottle會是另外一個不錯的選擇,但是我最近都在用Flask,所以我就選它了。
開始的時候,我需要決定如何來存放用戶和交易信息。我決定使用SQLAlchemy,因為sandman的原因,我比較熟悉它SQLAlchemy。Flask有一個插件叫Flask-SQLAlchemy,這使得結(jié)合使用兩者非常容易。因為我不需要任何花哨的數(shù)據(jù)庫操作,我選擇SQLite作為我的后臺數(shù)據(jù)庫。
決定這樣做之后,我創(chuàng)建了一個app.py 文件并且創(chuàng)建了如下的模型:
class Product(db.Model): __tablename__ = 'product' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String) file_name = db.Column(db.String) version = db.Column(db.String) is_active = db.Column(db.Boolean, default=True) price = db.Column(db.Float) class Purchase(db.Model): __tablename__ = 'purchase' uuid = db.Column(db.String, primary_key=True) email = db.Column(db.String) product_id = db.Column(db.Integer, db.ForeignKey('product.id')) product = db.relationship(Product) downloads_left = db.Column(db.Integer, default=5)
在向數(shù)據(jù)庫添加了5種不同版本的書籍后(我創(chuàng)建了一個populate_db.py 文件,并把這些版本作為SQLAlchemy模型來添加進數(shù)據(jù)庫),我需要決定我究竟要如何處理支付。幸運的是,Stripe讓接受一個信用卡變得非常簡單,并且我也已經(jīng)有了一個他們的賬戶。他們的”checkout.js”方案會在你的頁面上創(chuàng)建一個表單和按鈕。當點擊這個按鈕時,一個簡潔又引人注目的浮動層會彈出來。
這個表單的action 屬性指向你的站點的一個頁面,當用戶完成支付后就會被帶到那里。我在我的書籍銷售頁面添加了5個這樣的按鈕,還有一個隱藏的表單欄,包含了被交易產(chǎn)品的id(product_id)(1-5之間的一個整數(shù))
處理支付
顯然,我的應用需要一個后端來處理一次成功付款。我添加了以下這些函數(shù)來完成這一目的:
@app.route('/buy', methods=['POST']) def buy(): stripe_token = request.form['stripeToken'] email = request.form['stripeEmail'] product_id = request.form['product_id'] product = Product.query.get(product_id) try: charge = stripe.Charge.create( amount=int(product.price * 100), currency='usd', card=stripe_token, description=email) except stripe.CardError, e: return """<html><body><h1>Card Declined</h1><p>Your chard could not be charged. Please check the number and/or contact your credit card company.</p></body></html>""" print charge purchase = Purchase(uuid=str(uuid.uuid4()), email=email, product=product) db.session.add(purchase) db.session.commit() message = Message( subject='Thanks for your purchase!', sender="jeff@jeffknupp.com", html="""<html><body><h1>Thanks for buying Writing Idiomatic Python!</h1> <p>If you didn't already download your copy, you can visit <a >your private link</a>. You'll be able to download the file up to five times, at which point the link will expire.""".format(purchase.uuid), recipients=[email]) with mail.connect() as conn: conn.send(message) return redirect('/{}'.format(purchase.uuid))
正如你所看到的,我寫代碼的時候有點偷懶了(因為我正在憤怒的編程……)首先,我有一個內(nèi)聯(lián)HTML,在付費不成功時返回,還有已購買成功時返回郵箱。這些東西應該被放在一個全局變量,或者,更好的方法是放在一個單獨的文件中。另外,在創(chuàng)建Purchase 時我并沒有進行任何的錯誤檢查。但是實際上,唯一可能出錯的地方是嘗試插入重復的 uuid,但是我并不擔心,因為發(fā)生的幾率太低了(潛臺詞:微乎其微)。
你可以看的我使用了一個mail 對象,這個對象來自于Flask-Mail包,這個插件讓發(fā)送郵件變得輕松。我就設置它使用GMail作為服務器,然后所有東西都可以正常工作了。
好吧,現(xiàn)在該給我書了
現(xiàn)在,支付的部分已經(jīng)搞定,我需要添加一個后端功能,在完成支付后初始化下載。因為我使用UUID作為主鍵,所以我同樣可以使用它作為URL。當有人訪問包含UUID的URL時,我需要檢查該UUID是不是包含在數(shù)據(jù)庫中。如果是的話,提供書籍文件并且把剩余下載(downloads_left)次數(shù)屬性減少1次。如果不是的話,就返回404錯誤。下面是我寫的代碼:
@app.route('/<uuid>') def download_file(uuid): purchase = Purchase.query.get(uuid) if purchase: if purchase.downloads_left <= 0: return """<html><body><h1>No downloads left!</h1><p>You have exceeded the allowed number of downloads for this file. Please email jeff@jeffknupp.com with any questions.</p></body></html>""" purchase.downloads_left -= 1 db.session.commit() return send_from_directory(directory='files', filename=purchase.product.file_name, as_attachment=True) else: abort(404)
非常直觀。使用UUID作為一個URL變量,尋找交易信息。如果存在,就檢查是否還有可用下載次數(shù),然后提供所購買的文件。否則,等著你的是404錯誤。
最后,我需要我需要添加一個測試來讓我可以模擬交易過程。下面是測試代碼和讓這個app運行的代碼:
@app.route('/test') def test(): return """<http><body><form action="buy" method="POST"> <script src="https://checkout.stripe.com/checkout.js" class="stripe-button" data-key="pk_test_w3qNBkDR8A4jkKejBmsMdH34" data-amount="999" data-name="jeffknupp.com" data-description="Writing Idiomatic Python 3 PDF ($9.99)"> </script> <input type="hidden" name="product_id" value="2" /> </form> </body> </html> """ if __name__ == '__main__': sys.exit(app.run(debug=True))
能力越大…責任越大!
實際上我對于自己能如此快速簡單讓這一切工作起來感到吃驚。整個應用程序包含在一個100行代碼的文件中。而且它替代了我每天使用的那些重要服務,我對那些服務一直都不滿意。最后,我可以追蹤交易,沒有任何問題,這讓我確信自己可以提高銷量。
作為一個開發(fā)者,能意識到你有能力塑造我們和數(shù)字世界的交互是很好的一件事。比方說,我會忘記,如果有一些科技不能按照我預想的方式去工作,我有能力改變它。從自動化機械式的任務比如輸入數(shù)據(jù),到自動排序和整理電子郵件,開發(fā)者們有能力去簡化他們每天的工作。
擁有Flask這樣的工具對解決這些問題非常重要。正如像作為程序員那樣進步,你應該建立你的一套解決“核心”問題的工具集。Flask就是很好的例子,因為匆匆忙忙的拼湊一個web應用是一件司空見慣的事情。
當然,分享你的作品同樣非常的重要。如果我做一些東西,對我自己有用,但沒有去分享給別人,我就會怠慢?!胺窒怼辈粌H僅意味著”把項目放進一個GitHub公共倉庫“。你還需要讓大家知道有這個東西。從郵件列表到論壇再到個人博客,從來都不缺少讓大家知道你創(chuàng)造了一些東西的途徑。我總是設法回饋社區(qū),因為我從中得到了很多東西。
相關文章
解決django migrate報錯ORA-02000: missing ALWAYS keyword
這篇文章主要介紹了解決django migrate報錯ORA-02000: missing ALWAYS keyword,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-07-07解決Python3.8用pip安裝turtle-0.0.2出現(xiàn)錯誤問題
turtle庫是python的基礎繪圖庫,這個庫被介紹為一個最常用的用來給孩子們介紹編程知識的方法庫,這篇文章主要介紹了解決Python3.8用pip安裝turtle-0.0.2出現(xiàn)錯誤問題,需要的朋友可以參考下2020-02-02