オンライン支払いを扱うプロジェクトの一部のアーキテクチャに関する記事。 意図的に、特定の請求のAPIまたはその中の登録手順を詳細に説明したくありません。 特定の請求の微妙な点については個別に議論する必要があります。そうでない場合、トピックを明らかにすることはできません。 この記事の目的:新しいタイプの請求と支払いのタイプを最小限の頭痛でストリング化できるアーキテクチャーのバリエーションを議論すること。
まず、想像してみてください。私たちは少し考えて、私たちのサイトで請求システムの1つを介した商品の非常に単純な販売を行いました。
- 製品情報:製品ID、価格、<仕様>があります。
- ユーザーはサイトにアクセスして、購入ボタンをクリックします。 購入情報を保存します:購入ID、製品ID、その時点の製品価格、<顧客情報>;
- ユーザーは購入を監視し、そのうちの1つに対して「支払い」を押します。 支払い情報:支払いID、購入ID、支払い日、支払いステータス(、支払い金額)を保存し、ユーザーを課金システムに送信します。
- 請求応答を処理するスクリプトは、応答データ、応答ID、<請求が送信したすべてのもの>、応答日付、応答ステータスを保存します。 応答の有効性を確認し、確認の結果によって応答のステータスを保存します。 すべてが問題ない場合、「有料」のステータスを希望の購入に設定します
- 有料購入に関する情報は、「配信する必要があります」とマークされたモデレーターによって表示されます
* バイヤーに関する情報-これは、すべての必要なデータを検索できるユーザー番号である場合があります。または、ユーザーに登録の負荷をかけたくない場合は、直接データ(住所、電話番号など)にすることもできます。
これをすべてデバッグし、しばらくの間、作業に満足していました。 しかし、私たちはますます頻繁に耳にします:請求をきちんと締める必要があるでしょう。 さらに、商品だけでなく、さまざまな種類のアカウントをユーザーに販売し、評価を10ポイント上げたい場合などに個別に支払いを許可することも必要です。 私たちはそれをショッピングと呼びますが、購入は今では別のタイプのものであることに留意してください。
ご存知のように、その後、良い考えが生まれます。 いくつかの請求書を締めて、いくつかの異なるタイプの購入を整理する必要があった場合にのみ、私のシステムでは、請求書の処理から購入の処理が分離され、異なる請求書を処理する際の一般的な部分、異なる種類の購入の処理のパターンが区別されました。
購入処理と請求処理を分離することにより、
- システムに存在する購入の種類の数に関係なく、新しい請求書を1か所で一度に接続します。
- 新しいタイプの購入は、請求の数に関係なく、一度に1か所で接続します。
さまざまなタイプの購入を処理する場合、それらすべてをコンポーネントに分割できることがわかります。
- 特定の製品に関する情報は、システムで利用できます:ID(このタイプに固有)、価格、<特性>。 これは、ストア内の製品の説明、アカウントのタイプとその有効期間の説明、または格付けをNポジション上げるサービスの説明です。
- ユーザーの選択に関する情報(どのユーザー、どのタイプの製品、どの製品番号を選択したか)を保存します。
- 購入ステータスの変更(支払い済み、削除済み、...);
- 購入の実装、それを呼び出しましょう。 (例:商品の配送、指定された期間の口座タイプの変更、またはNポジションの格付けの増加);
ここで、パラグラフ1と4のみに基本的な違いがあることが明らかです。購入のタイプと購入のアクションを説明するクラスのインターフェイスに従うと、さまざまなタイプの購入の処理スキームが1つになります。
課金システムの操作は、次のポイントに分けることができます。
- 支払い情報の保存:支払いID、請求タイプ、購入ID、支払いステータス、<その他の特性>。
- ユーザーを課金システムにリダイレクトし、支払い番号と購入金額を示します。
- 請求からの応答の有効性の確認。
- 支払いステータスの変更。
- すべて問題なければ、購入処理を呼び出します(購入ステータスの変更、購入の実装など)。
説明した構造を視覚的に表示するためのクラス図:
これらは私の仕事で従おうとする一般原則です。 このスキームは改善できるし、改善すべきだと思います。 建設的な議論を楽しみにしています。
記事の最初の部分では、一般的な単語だけでなく、特定のコード行も予想されることを覚えています。 およそ、この設計のコードは次のとおりです。 主要なポイントを強調するために、コンテキストから取り出されて最大限にトリミングされた実際の例。 残念ながら、たとえそうであっても、それは少し多すぎるコードになりました:)
別の言語ですぐに予約を行うと、抽象クラスとその子孫を使用できますが、PHPで静的関数をオーバーライドできないため、祖先はインターフェイス+基本クラスに分割されました。
有料会員チップを実装するためのショッピングインターフェイスと例:
interface InterfacePurchase {
public function getId();
public function getItemId();
public function setItemId ($val);
public function getItemType();
public function setItemType ($val);
public function getPrice();
public function setPrice ($val);
public function getUserId();
public function setUserId($val);
public function getStatus();
public function setStatus($val);
public function save ();
/**
*
*/
public function callbackPayment ();
/**
* -. ,
*/
public function getItem ();
}
class CPurchase {
protected $_mPurchase = null ;
/**
* @return InterfacePurchase
**/
public static function createPurchaseByType ($type) {
$purchase = null ;
switch ($type){
case PURCHASE_SHOP: $purchase = new CPurchaseShop(); break;
case PURCHASE_ACCOUNT: $purchase = new CPurchaseAccount(); break;
case PURCHASE_RAIT: $purchase = new CPurchaseRait(); break;
// ...
default : throw new ExceptionUnknownPurchaseType (__CLASS__);
}
$purchase->_mPurchase = new CPurchaseItem ();
return $purchase;
}
/**
* @return InterfacePurchase
**/
public static function loadPurchaseById($id){
$purchase_item = CPurchaseItem::getById($id);
$purchase = self::createPurchaseByType($purchase_item->getType());
$purchase->_mPurchase = $purchase_item;
}
public function getId() { return $ this ->_mPurchase->getId(); }
public function getItemId() { return $ this ->_mPurchase->getItemId();}
public function setItemId ($val) { return $ this ->_mPurchase->setItemId( $val ); }
public function getItemType() { return $ this ->_mPurchase->getItemType(); }
public function setItemType ($val) { return $ this ->_mPurchase->setItemType( $val ); }
public function getPrice() { return $ this ->_mPurchase->getPrice (); }
public function setPrice ($val) { return $ this ->_mPurchase->setPrice ( $val ); }
public function getUserId() { return $ this ->_mPurchase->getUserId(); }
public function setUserId($val) { return $ this ->_mPurchase->setUserId($val); }
public function getStatus() { return $ this ->_mPurchase->getStatus(); }
public function setStatus($val) { return $ this ->_mPurchase->setStatus($val); }
public function save () { $ this ->_mPurchase->save(); }
}
Class CPurchaseAccount extends CPurchase implements InterfacePurchase {
public function getItem (){
$item = null ;
If ($item_id = $ this ->getItemId()) {
$item = CMembership::getById($item_id);
}
return $item;
}
public function callbackPayment () {
$ this ->setStatus(PURCHASE_STATUS_OK);
ServiceAccount::setMembership($ this ->getUserId(), $ this ->getItemId());
}
}
* This source code was highlighted with Source Code Highlighter .
請求インターフェイスとRoboxでの作業を実装するための例:
interface InterfaceBilling {
public function getId();
public function getPurchaseId();
public function setPurchaseId ($val);
public function getBillingType();
public function setBillingType ($val);
public function getStatus();
public function setStatus($val);
public function save ();
/**
*
*/
public function redirectToBilling ();
/**
* , ,
*/
public static function checkResponseFormat ($data);
/**
*
*/
public function checkResult ($data);
/**
* . , .
*/
public function addResultInView ($view, $results);
}
class CBilling {
protected $_mBilling = null ;
/**
* @return InterfaceBilling
**/
public static function createBillingByType( $type ) {
switch ($type){
case BILLING_ROBOX: $billing = new CBillingRobox(); break;
case BILLING_WM: $billing = new CBillingWM(); break;
// ...
default : throw new ExceptionUnknownBillingType (__CLASS__);
}
$billing->_mBilling = new CBillingItem();
$ this ->setBillingType($type);
}
public static function getBillingTypeByRequest($response_data) {
$billing_type = null ;
if (CBillingRobox::checkResponseFormat($response_data)) {
$billing_type = self::BILLING_ROBOX;
}
if (CBillingWM::checkResponseFormat($response_data)) {
$billing_type = self::BILLING_WM;
}
return $billing_type;
}
public function getId() { return $ this ->_mBilling->getId(); }
public function getPurchaseId() { return $ this ->_mBilling->getPurchaseId(); }
public function setPurchaseId ($val) { return $ this ->_mBilling->setPurchaseId($val); }
public function getBillingType() { return $ this ->_mBilling->getBillingType(); }
public function setBillingType ($val) { return $ this ->_mBilling->setBillingType($val); }
public function getStatus() { return $ this ->_mBilling->getStatus(); }
public function setStatus($val) { return $ this ->_mBilling->setStatus($val); }
public function save () { $ this ->_mBilling->save(); }
public function checkSumm($summ) {
$purchase = CPurchaseItem::getById($ this ->getPurchaseId());
return intval($purchase->getPrice()) == intval($summ);
}
public function checkStatusNotFinish() {
$purchase = CPurchaseItem::getById($ this ->getPurchaseId());
return PURCHASE_STATUS_OK != $purchase->getStatus();
}
}
class CBillingRobox extends CBilling implements InterfaceBilling {
public function redirectToBilling () {
$redirect_uri = Config::getKey( 'pay_uri' , 'robox' );
$purchase = CPurchaseItem::getById($ this ->getPurchaseId());
$hash = array(
'MrchLogin' => Config::getKey( 'merchant_login' , 'robox' ),
'OutSum' => $purchase->getPrice(),
'InvId' => $ this ->getId(),
'SignatureValue' => $ this ->_getSignatureValue()
);
MyApplication::redirect($redirect_uri, $hash);
}
public static function checkResponseFormat ($data) {
$is_id = isset($data[ 'InvId' ]);
$is_summ = isset($data[ 'OutSum' ]);
$is_resp_crc = isset($data[ 'SignatureValue' ]);
$result = $is_id && $is_summ && $is_resp_crc;
return $result;
}
public function checkResult ($data) {
$billing_item_id = isset($data[ 'InvId' ])? $data[ 'InvId' ]:0;
$summ = isset($data[ 'OutSum' ])? $data[ 'OutSum' ]:0;
$result = FALSE;
$purchase = null ;
try {
$ this ->_mBilling = CBillingItem::sgetById($billing_item_id);
$purchase = CPurchase::loadPurchaseById($ this ->getPurchaseId());
} catch (ExObjectNotFound $e) {}
if ($ this ->_mBilling && $purchase) {
$is_valid_control_summ = $ this ->_checkControlSumm($data);
$is_valid_summ = $ this ->_checkSumm($summ);
$is_valid_status = $ this ->_checkStatusNotFinish();
if ($is_valid_control_summ && $is_valid_summ && $is_valid_status) {
$result = TRUE;
$ this ->callbackPayment();
$purchase->callbackPayment();
}
}
return $result;
}
public function addResultInView ($view, $result) {
if ($result && $ this ->getId()) {
$view->addText( "OK" );
$view->addText($ this ->getId());
} else {
$view->addText( "ERROR" );
}
}
private function _getSignatureValue() {
$purchase = CPurchaseItem::getById($ this ->getPurchaseId());
$hash = array(
Config::getKey( 'merchant_login' , 'robox' ) ,
$purchase->getPrice(),
$ this ->getId(),
Config::getKey( 'merchant_password1' , 'robox' )
);
return md5(join( ':' , $hash));
}
private function checkControlSumm($data) {
$resp_crc = isset($data[ 'SignatureValue' ])? $data[ 'SignatureValue' ]:0;
return strtoupper(self::getControlSumm($data)) == strtoupper($resp_crc);
}
static public function getControlSumm($data) {
$hash = array(
isset($data[ 'OutSum' ])? $data[ 'OutSum' ]: '' ,
isset($data[ 'InvId' ])? $data[ 'InvId' ]: '' ,
Config::getKey( 'merchant_password2' , 'robox' )
);
return md5(join( ':' , $hash));
}
}
* This source code was highlighted with Source Code Highlighter .
このアーキテクチャの使用例:
class ModuleBilling {
private function _createResponse(){
// ,
}
// , :
public function actionResultPage () {
$response = $ this ->_createResponse();
$response_data = $_REQUEST;
$view = new View();
if ( $billing_type = CBilling::getBillingTypeByRequest( $response_data ) ) {
$billing = CBilling::createBillingByType($billing_type);
$result = $billing->checkResult($response_data);
if ($result){
$response->setStatus(CResponse::STATUS_OK);
} else {
$response->setStatus(CResponse::STATUS_ERROR);
}
$response->save();
$billing->addResultInView($view, $result);
}
return $view;
}
// :
public function actionBilling($req = array()){
$user = ServiceUser::checkAccess();
$billing_type = Request::getQueryVar( 'type' );
$purchase_id = Request::getQueryVar( 'purchase' );
$purchase = CPurchase::loadPurchaseById($purchase_id);
$purchase->setStatus(PURCHASE_STATUS_WAITMONEY);
$purchase->save();
$billing = CBilling::createBillingByType($billing_type);
$billing->setPurchaseId($purchase_id);
$billing->setStatus(BILLING_STATUS_WAITMONEY);
$billing->save();
$billing->redirectToBilling();
}
}
// :
...
$action = new ModuleBilling ();
$action->actionResultPage();
...
* This source code was highlighted with Source Code Highlighter .