Node.js上のQiwiのIPN

Qiwi支払いゲートウェイを介した支払いの可能性を実現するには、ロシア語の開発者向けガイドを読むだけで十分です。 しかし、期限があり、開発に多くの時間を費やしたくない人のために、コードを使用して自分の計算で開発プロセスを促進しようとします。



初期データ-Node.js-0.12.4、Sails-v0.12.11



開発を開始するには、 https://ishop.qiwi.comで登録してアカウントの確認を待つ必要があります 。 アカウントを確認したら、[設定]→[プロトコル]→[RESTプロトコル]に移動し、[認証データ]タブでプロジェクトID-ストアID(SHOP_ID)を確認してREST応答を確認する必要があります。 さらに、「新しいIDを生成」をクリックして、Qiwi APIへのRESTリクエストのAPI_IDを生成する必要があります。 パスワード(API_PWD)を書き留めておく必要があることに注意してください。そうすれば、それを見る場所がなくなります。



最初に、プログラマを混乱させ、QiwiにはPaypalなどのサンドボックスがないことを通知します。すべての作業は、最初は実際のお金とカードを使用してライブサーバーで行われます。



まず、請求書リクエストを送信する方法を学びます。 簡単に言うと、支払いプロセス全体は、請求、支払い用リンクの受信、クライアントがサービスの支払いを行うサイトへの移動、Qiwi IPNサーバーからの応答サーバーの待機で構成されます。



// AccountController.js module.exports = { // action for payment request qw_activate: function (req, res) { var user_id = user.id; var bill_id = user_id +'_'+ Date.now(), order_lifetime_days = 1, successUrl = req.param('success_return_url'), // redirect URL in case of success payment failUrl = req.param('fail_return_url'); // redirect URL in case of payment is failed var url = sails.config.custom_config.QIWI.API_URL+sails.config.custom_config.QIWI.SHOP_ID+'/bills/'+bill_id, request = require('request'), querystring = require('querystring'); var request_data = {headers: { "Accept": "text/json", "Authorization": 'Basic '+new Buffer( sails.config.custom_config.QIWI.API_ID +':'+ sails.config.custom_config.QIWI.API_PWD ).toString('base64'), "Content-Type": "application/x-www-form-urlencoded; charset=utf-8" }}; request_data.url = url; var lifetime = new Date(); lifetime.setHours(lifetime.getHours() + 24 * order_lifetime_days); request_data.body = querystring.stringify({ user: 'tel:'+req.param('phone').replace(/[\(\)]/g, ""), amount: sails.config.custom_config.QIWI.member_pro_membership_cost, ccy: sails.config.custom_config.QIWI.CURRENCY, // RUB || USD comment: "Payment for service by "+user.email, lifetime: lifetime.toISOString(), pay_source: 'qw', // 'mobile' prv_name: 'email@mail.ru' }); request.put(request_data, function (err, data) { if(err) return res.badRequest(err); // q if(JSON.parse(data.body).response.result_code == 0) { return res.ok({ url: 'https://qiwi.com/order/external/main.action?shop='+sails.config.custom_config.QIWI.SHOP_ID+'&transaction='+bill_id+'&successUrl='+successUrl+'&failUrl='+failUrl+'&iframe=false' }); } res.badRequest({ message: JSON.parse(data.body).response.description }); }); } }
      
      





次に、アカウントを作成するリクエストを送信します-支払いのリンクを取得し、クライアントをQiwiウェブサイトに送信します。 クライアントがQiwi Webサイトで支払いを行った後、支払いの結果に応じて、クライアントは支払いリンクに示されたsuccessUrlまたはfailUrlページにリダイレクトします。 支払いの結果(キャンセル、成功、遅延、エラーなど)に関係なく、当社のサーバーはQiwi IPNサーバーからの応答を受信するために開いています。 回答はhttpsとhttpの両方にすることができます。 APIサーバーをhttpsに転送できる場合-このプロトコルを使用することをお勧めします-より安全です。



コードには、https経由で応答をチェックするコードの一部がありますが、検証されていません。このコードは、私の基礎として使用できます。 サーバーへのhttpまたはhttpsを介して支払いステータスに関する回答を受け取るには、Qiwi個人アカウント設定の「プル(REST)プロトコル設定」セクションを構成する必要があります。 通知を有効にし、通知のURLを提供する必要があります。 httpのポートは80のみ、httpsの場合は443です。他のポートを指定することはできません。 「通知パスワードの変更」をクリックして、通知用のパスワードを生成する必要があります。 その後、コードの記述を開始できます。



 // AccountController.js module.exports = { // ipn action qw_ipn: function (req, res) { var Q = require('q'), THIS = this; (function (req, res) { var deferred = Q.defer(); var reqParams = req.allParams(); var UID = reqParams.bill_id ? reqParams.bill_id.split('_')[0] : null, payment_date = reqParams.bill_id ? new Date(parseInt(reqParams.bill_id.split('_')[1])) : null, txn_id = "txn_" + reqParams.bill_id, txn_status = reqParams.status; (function() { var deferred2 = Q.defer(); if (typeof req.headers.authorization !== 'undefined') { // Basic authorization if (req.headers.authorization == 'Basic ' + new Buffer(sails.config.custom_config.QIWI.SHOP_ID + ':' + sails.config.custom_config.QIWI.NOTIFICATION_PWD).toString('base64')) { deferred2.resolve(); } else { deferred2.reject(150); // Error in password verification } } else if (typeof req.headers['x-api-signature'] !== 'undefined') { // digital sign // TODO: code not verified var crypto = require('crypto'), hexHash, signature = req.headers['x-api-signature'], encoded_signature, reqString = ""; var sortedIndexes = Object.keys(reqParams).sort(); // sort keys // generate string from values of sorted request for (var i in sortedIndexes) { reqString += "|" + reqParams[sortedIndexes[i]]; } reqString = THIS._convertUTF16ToUTF8ToByteStr(reqString.substring(1)); // convert UTF16 string to UTF8 and then to string of bytes hexHash = crypto.createHmac('sha1', THIS._convertUTF16ToUTF8ToByteStr(sails.config.custom_config.QIWI.SHOP_ID)).update(reqString).digest('hex'); // hashed string hexadecimal encoded_signature = new Buffer(THIS._convertUTF16ToUTF8ToByteStr(hexHash)).toString('base64'); // base64 encoded if (encoded_signature == signature) { // compare encoded signature with signature from header deferred2.resolve(); } else { deferred2.reject(151); // Error in sign verification } } return deferred2.promise; })().then(function() { if(parseFloat(reqParams.amount) !== sails.config.custom_config.QIWI.member_pro_membership_cost) return deferred.resolve(0); // ignore creating transactions for commission Transaction.findOne({txn_id: txn_id, payment_status: txn_status}).exec(function (err, found) { if (err) return deferred.reject('Invalid updating payment status. Error: ' + err); (function() { var deferred3 = Q.defer(); if (!found) { var params = { txn_id: txn_id, txn_type: reqParams.command, // "bill" mc_gross: reqParams.amount, mc_currency: reqParams.ccy, payment_date: payment_date, payment_status: reqParams.status, business: reqParams.prv_name, receiver_email: reqParams.prv_name, payer_id: UID, payer_email: reqParams.user, custom: JSON.stringify({error: reqParams.error}), gateway_type: Transaction.attributes.gateway_type.in[1] // qiwi gateway }; // first payment Transaction.create(params).then(function (created) { if (created) { deferred3.resolve(); } }).catch(function (err) { if (err) deferred3.reject('Invalid transaction creation. Error: ' + err); }); } else { // already exists deferred.resolve(0); } return deferred3.promise; })().then(function() { if (parseFloat(reqParams.amount) == sails.config.custom_config.QIWI.member_pro_membership_cost && reqParams.ccy == sails.config.custom_config.QIWI.CURRENCY) { Model.findOne({id: UID}).then(function (found_user) { if(found_user) { switch(reqParams.status) { case 'paid': // mark user as paid ... break; case 'rejected': // mark user as unpaid if he was rejected payment ... break; } } else { if (err) return deferred.reject('User not found. Error: ' + err); } }).catch(function (err) { if (err) return deferred.reject('Error while searching user. Error: ' + err); }); } else { deferred.reject('Not valid currency or payment amount.'); } }, function(err) { deferred.reject('Error while transaction creation. Error: ' + err); }); }); }, function(err) { deferred.reject(err); }); return deferred.promise; })(req, res).then(function (result_code) { res.setHeader("Content-type", "text/xml"); var xml = '<?xml version="1.0"?>\ <result>\ <result_code>' + result_code + '</result_code>\ </result>'; return res.send(xml); }, function (error) { console.log(error); res.setHeader("Content-type", "text/xml"); var errNum = typeof error == 'number' ? error : 13; var xml = '<?xml version="1.0"?>\ <result>\ <result_code>' + errNum + '</result_code>\ </result>'; return res.send(xml); }); }, /** * Convert UTF16 string to UTF8 and then to bytes * @param str * @returns {string} * @private */ _convertUTF16ToUTF8ToByteStr: function (str) { var utf8 = unescape(encodeURIComponent(str)); var byteString = ""; for (var i = 0; i < utf8.length; i++) { byteString += utf8.charCodeAt(i); } return byteString; } }
      
      





コードは複雑ではありませんが、いくつかの質問が発生する可能性があります。これについては、以下で予測して回答を示します。



Qiwi IPNサーバーは、応答が結果コード0とHTTPステータスコード200を返すまで、日中に間隔を空けてリクエストを繰り返します(試行回数50回のみ)。支払いの重複を避けるため、最初の通知を受け取ったときに、将来、トランザクションがアカウント番号でトランザクションを作成しますこのようなアカウントが存在する-私はリクエストをドロップします。 また、支払いと払い戻し、つまり「支払い済み」と「拒否済み」の支払いステータスにも興味があります。



リクエストのタイプを理解するために、アクションへのルートを投稿します。



 // routes.js module.exports.routes = { 'POST /qw_ipn': 'AccountController.qw_ipn', 'POST /qw_activate': 'AccountController.qw_activate' }
      
      





これで私の短い最初の投稿は終わりです。 Qiwi APIのドキュメントでコードを読むことをお勧めします。すべてのエラー番号、ビジネスロジックなどが記述されています。 どんなコメントでも喜んでいます。 批判が大好きです。



All Articles