Alipayによる支払い処理
ユーザーが宿泊予約を確定した後、ユーザーは「支払い」ボタンをクリックして支払いを完了することができます。ここではAlipayを使用した支払い方法のみを実装します。
Alipayの開発者向けページを開き、必要なAPIドキュメントとチュートリアルを確認できます。主な手順は以下の通りです:
- Alipayのテスト環境(サンドボックス環境)でテストを行います。正式環境ではなく、サンドボックス環境にはテスト用のアプリケーションとアカウントが提供されています。
- Alipay開発者ツールを使用してRSA2鍵ペア(アプリケーションプライベートキーとアプリケーションパブリックキー)を生成し、アプリケーションパブリックキーをサンドボックスアプリケーションに追加します。
- Alipay公式SDKをダウンロードします。Python用のSDKは以下のURLから入手可能です:https://pypi.org/project/alipay-sdk-python/3.3.398/
- 必要なAPIを見つけて使用します。ここではモバイルウェブサイト向けの支払いAPI
alipay.trade.wap.payを使用します。
Alipay支払いのバックエンドロジック
ihome/api_1_0ディレクトリ下に支払いモジュールのビューファイルpay.pyを作成し、APIブループリントにインポートします。
支払い機能の実装
# ihome/api_1_0/pay.py
from alipay.aop.api.AlipayClientConfig import AlipayClientConfig
from alipay.aop.api.DefaultAlipayClient import DefaultAlipayClient
from alipay.aop.api.domain.AlipayTradeWapPayModel import AlipayTradeWapPayModel
from alipay.aop.api.request.AlipayTradeWapPayRequest import AlipayTradeWapPayRequest
import logging
def initialize_alipay_client():
"""Alipayクライアントの初期化"""
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s %(levelname)s %(message)s',
filemode='a', )
logger = logging.getLogger('')
config = AlipayClientConfig()
config.server_url = constants.ALIPAY_SERVER_URL
config.app_id = constants.ALIPAY_APP_ID
config.app_private_key = constants.ALIPAY_PRIVATE_KEY
config.alipay_public_key = constants.ALIPAY_PUBLIC_KEY
client = DefaultAlipayClient(config, logger)
return client
def create_alipay_model(order):
"""Alipayモデルの作成"""
model = AlipayTradeWapPayModel()
model.out_trade_no = order.id
model.total_amount = str(order.amount)
model.subject = "愛家租房"
model.product_code = constants.PRODUCT_CODE
model.timeout_express = constants.ALIPAY_EXPRESS
return model
def execute_alipay(client, model):
"""Alipay支払いの実行"""
request = AlipayTradeWapPayRequest(biz_model=model)
request.return_url = constants.ALIPAY_RETURN_URL
pay_url = client.page_execute(request, http_method='GET')
return pay_url
注:
- Alipay SDKの呼び出し例に基づいて、クライアントの作成、リクエストの構築、APIの実行、結果の解析という4つのステップがあります。
- 異なるAlipay APIでも基本的な構造は同じですが、異なるAPIに対応するModelとRequestクラスが必要です。
- それぞれのステップに対応する関数として、
initialize_alipay_client()、create_alipay_model()、execute_alipay()を定義しています。 initialize_alipay_client()は、constants.pyに定義された設定を使用してAlipayクライアントを初期化します。
# constants.py
ALIPAY_SERVER_URL = 'https://openapi.alipaydev.com/gateway.do'
ALIPAY_APP_ID = '2016102200739747'
ALIPAY_PRIVATE_KEY = 'MIIEpAIBAAK.................'
ALIPAY_PUBLIC_KEY = 'MIIBIjANBgkq.................'
ALIPAY_EXPRESS = '10m'
PRODUCT_CODE = 'QUICK_WAP_WAY'
ALIPAY_NOTIFY_URL = ""
ALIPAY_RETURN_URL = "http://127.0.0.1:5000/payresult.html"
支払いAPIの実装
支払い機能を実装したら、次に完全なバックエンドAPIを実装します。支払いモジュールのビューファイルpay.pyにAPIを追加します。URLは/api/v1.0/orders/alipayで、リクエストメソッドはPOSTです。
# ihome/api_1_0/pay.py
@api.route('/orders/alipay', methods=['POST'])
@login_required
def create_alipay_order():
data = request.get_json()
if not data:
return parameter_error()
order_id = data.get('order_id')
if not order_id:
return parameter_error()
try:
order = Orders.query.get(order_id)
except Exception as e:
current_app.logger.error(e)
return jsonify(errno=RET.DBERR, errmsg='オーダー取得エラー')
if not order:
return jsonify(errno=RET.PARAMERR, errmsg='オーダーIDが存在しません')
if order.user != g.user:
return jsonify(errno=RET.PARAMERR, errmsg='このオーダーは現在のユーザーに属していません')
try:
client = initialize_alipay_client()
model = create_alipay_model(order)
pay_url = execute_alipay(client, model)
except Exception as e:
current_app.logger.error(e)
return jsonify(errno=RET.THIRDERR, errmsg='支払いAPI呼び出しエラー')
return jsonify(errno=RET.OK, data={'url': pay_url})
フロントエンドでの支払い処理
ユーザーが「支払い」ボタンをクリックすると、Ajaxリクエストが送信され、バックエンドAPIを呼び出します。
// 支払いボタン
$('.order-pay').on("click", function () {
var orderId = $(this).parents("li").attr("order-id");
$.ajax({
url: '/api/v1.0/orders/alipay',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({order_id: orderId}),
headers: {'X-CSRFToken': getCookie('csrf_token')},
dataType: 'json',
success: function (resp) {
if (resp.errno == '0'){
window.location.href = resp.data.url;
} else {
alert(resp.errmsg);
}
}
})
});
支払い完了後の処理
支払いが完了すると、ユーザーは「完了」ボタンをクリックして設定されたRETURN_URLにリダイレクトされます。通常、このURLは中間ページであり、システムのオーダーステータスやその他の情報を更新するために使用されます。更新が完了したら、「マイオーダー」ページに自動的にリダイレクトされます。
<div class="container">
<div class="top-bar">
<div class="nav-bar">
<h3 class="page-title">支払い結果</h3>
<a class="nav-btn fl" href="#" onclick="hrefBack();"><span><i class="fa fa-angle-left fa-2x"></i></span></a>
</div>
</div>
<div class="house-info">
<h1>支払い成功</h1>
<a href="orders.html">マイオーダーページへ移動(<span id="num">3</span>秒後に自動移動)</a>
</div>
</div>
$(document).ready(function(){
var url_params = document.location.search.substr(1);
$.ajax({
url: 'api/v1.0/orders/alipay',
type: 'PATCH',
data: url_params,
headers: {'X-CSRFToken': getCookie('csrf_token')},
dataType: 'json',
success: function (resp) {
if (resp.errno == '0'){
function jump(count) {
setTimeout(function () {
count--;
if (count > 0) {
$('#num').text(count);
jump(count);
} else {
location.href = "orders.html";
}
}, 1000);
}
jump(3);
} else {
alert(resp.errmsg);
}
}
});
})
支払い完了後のバックエンドロジック
支払いが完了した後、オーダーステータスを更新するためのAPIを実装します。
# ihome/api_1_0/pay.py
@api.route('/orders/alipay', methods=['PATCH'])
@login_required
def update_order_status():
data = request.form.to_dict()
if not data:
return parameter_error()
if not check_signature(data):
return jsonify(errno=RET.PARAMERR, errmsg='データ検証エラー')
order_id = data.get('out_trade_no')
trade_no = data.get('trade_no')
try:
order = Orders.query.get(order_id)
except Exception as e:
current_app.logger.error(e)
return jsonify(errno=RET.DBERR, errmsg='オーダー取得エラー')
if not order:
return jsonify(errno=RET.PARAMERR, errmsg='オーダーIDが存在しません')
if order.status != 'WAIT_PAYMENT':
return jsonify(errno=RET.PARAMERR, errmsg='オーダーステータスが"待機中"ではありません')
if order.user != g.user:
return jsonify(errno=RET.PARAMERR, errmsg='このオーダーは現在のユーザーに属していません')
order.status = 'WAIT_COMMENT'
order.trade_no = trade_no
try:
db.session.add(order)
db.session.commit()
except Exception as e:
current_app.logger.error(e)
return jsonify(errno=RET.DBERR, errmsg='オーダー更新エラー')
return jsonify(errno=RET.OK)
署名の検証
SDKは署名検証のためのverify_with_rsaメソッドを提供しています。このメソッドを使用して署名を検証します。
from alipay.aop.api.util.SignatureUtils import verify_with_rsa
def check_signature(params):
sign = params.pop('sign')
params.pop('sign_type')
params = sorted(params.items(), key=lambda x: x[0], reverse=False)
message = '&'.join(f'{k}={v}' for k, v in params).encode()
try:
result = verify_with_rsa(constants.ALIPAY_PUBLIC_KEY.encode('utf-8'), message, sign)
return result
except Exception as e:
current_app.logger.error(e)
return False
注:
signパラメータのみを削除し、sign_typeは削除しないようにします。verify_with_rsaが成功するとTrueを返し、失敗すると例外を投げます。