Flaskアプリケーションにおける支払い処理の実装: 支払いモジュール(Alipay)

Alipayによる支払い処理

ユーザーが宿泊予約を確定した後、ユーザーは「支払い」ボタンをクリックして支払いを完了することができます。ここではAlipayを使用した支払い方法のみを実装します。

Alipayの開発者向けページを開き、必要なAPIドキュメントとチュートリアルを確認できます。主な手順は以下の通りです:

  1. Alipayのテスト環境(サンドボックス環境)でテストを行います。正式環境ではなく、サンドボックス環境にはテスト用のアプリケーションとアカウントが提供されています。
  2. Alipay開発者ツールを使用してRSA2鍵ペア(アプリケーションプライベートキーとアプリケーションパブリックキー)を生成し、アプリケーションパブリックキーをサンドボックスアプリケーションに追加します。
  3. Alipay公式SDKをダウンロードします。Python用のSDKは以下のURLから入手可能です:https://pypi.org/project/alipay-sdk-python/3.3.398/
  4. 必要な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

注:

  1. Alipay SDKの呼び出し例に基づいて、クライアントの作成、リクエストの構築、APIの実行、結果の解析という4つのステップがあります。
  2. 異なるAlipay APIでも基本的な構造は同じですが、異なるAPIに対応するModelとRequestクラスが必要です。
  3. それぞれのステップに対応する関数として、initialize_alipay_client()create_alipay_model()execute_alipay()を定義しています。
  4. 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

注:

  1. signパラメータのみを削除し、sign_typeは削除しないようにします。
  2. verify_with_rsaが成功するとTrueを返し、失敗すると例外を投げます。

タグ: flask alipay Python 支払い処理 API

6月17日 20:30 投稿