diff --git a/composer.json b/composer.json index 3c04976..6906f07 100644 --- a/composer.json +++ b/composer.json @@ -40,14 +40,6 @@ "payum/payum": "0.7.*@dev", "yiisoft/yii": "~1" }, - "suggest": { - "payum/paypal-express-checkout-nvp": "If you want to use paypal express checkout nvp gateway", - "payum/paypal-pro-checkout-nvp": "If you want to use paypal pro checkout nvp gateway", - "payum/authorize-net-aim": "If you want to use authorize.net gateway", - "payum/be2bill": "If you want to use be2bill gateway", - "payum/payex": "If you want to use payex gateway", - "payum/omnipay-bridge": "If you want to use omnipay provided gateways" - }, "autoload": { "psr-0": { "Payum\\YiiExtension": "" } }, diff --git a/docs/how-to-use-active-record-storage.md b/docs/how-to-use-active-record-storage.md new file mode 100644 index 0000000..4055ff3 --- /dev/null +++ b/docs/how-to-use-active-record-storage.md @@ -0,0 +1,175 @@ +# How to use active record storage + +## Configuration + +_**Note**: We assume you already [install the extension](installation.md) correctly._ + +In the _app/config/main.php_ you have to configure payum extensions. +In general you define model storages and payments. +Your configuration may look like this: + +```php +array( + 'payment'=>array( + 'class'=>'Payum\YiiExtension\PaymentController', + ), + ), + 'components' => array( + 'payum' => array( + 'class' => '\Payum\YiiExtension\PayumComponent', + 'tokenStorage' => new Payum\YiiExtension\Storage\ActiveRecordStorage( + 'TOKEN_TABLE_NAME', 'Payum\YiiExtension\Model\PaymentSecurityToken' + ), + 'payments' => array( + 'paypal' => PaymentFactory::create(new Api(new Curl(), array( + 'username' => 'REPLACE WITH YOURS', + 'password' => 'REPLACE WITH YOURS', + 'signature' => 'REPLACE WITH YOURS', + 'sandbox' => true + ))) + ), + 'storages' => array( + 'paypal' => array( + 'Payum\Paypal\ExpressCheckout\Nvp\Model\PaymentDetails' => new Payum\YiiExtension\Storage\ActiveRecordStorage( + 'PAYMENT_DETAILS_TABLE_NAME', + 'Payum\YiiExtension\Model\PaymentDetailsActiveRecordWrapper' + ), + ) + ) + ), + ), +); +``` +You will need to create database tables for token storage and payment details storage. + +The token storage table can be created in a MySQL database with the following SQL: + +``` +-- +-- Table structure for table `TOKEN_TABLE_NAME` +-- + +CREATE TABLE IF NOT EXISTS `TOKEN_TABLE_NAME` ( + `_hash` varchar(255) NOT NULL DEFAULT '', + `_payment_name` varchar(255) DEFAULT NULL, + `_details` varchar(255) DEFAULT NULL, + `_after_url` varchar(255) DEFAULT NULL, + `_target_url` varchar(255) DEFAULT NULL, + PRIMARY KEY (`_hash`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +``` + +The payment details table can be created as follows: +``` +-- +-- Table structure for table `PAYMENT_DETAILS_TABLE_NAME` +-- + +CREATE TABLE IF NOT EXISTS `PAYMENT_DETAILS_TABLE_NAME` ( + `_id` int(11) NOT NULL AUTO_INCREMENT, + `_details` text, + PRIMARY KEY (`_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 ; +``` +The same table can be used to store payment details for multiple payment methods (e.g. Paypal, AuthorizeNet, etc.) +The payment details are serialized and stored in _details. + +_**Note**: We use paypal as an example. You may configure any other payment you want._ + +## Usage +(The following code is unchanged from using FileSystemStorage - implementing ActiveRecordStorage +doesn't require changes to your code). +Here is the class you want start with. +The prepare action create payment details model. +Fill it with amount and currency and creates some tokens. +At the end it redirects you to payum capture controller which does all the job. +The done action is your landing action after the capture is finished. +Here you want to check the status and do your business actions depending on it. + +```php +getPayum(); + + $tokenStorage = $payum->getTokenStorage(); + $paymentDetailsStorage = $payum->getRegistry()->getStorageForClass( + 'Payum\Paypal\ExpressCheckout\Nvp\Model\PaymentDetails', + $paymentName + ); + + $paymentDetails = $paymentDetailsStorage->createModel(); + $paymentDetails['PAYMENTREQUEST_0_CURRENCYCODE'] = 'USD'; + $paymentDetails['PAYMENTREQUEST_0_AMT'] = 1.23; + $paymentDetailsStorage->updateModel($paymentDetails); + + $doneToken = $tokenStorage->createModel(); + $doneToken->setPaymentName($paymentName); + $doneToken->setDetails($paymentDetailsStorage->getIdentificator($paymentDetails)); + $doneToken->setTargetUrl( + $this->createAbsoluteUrl('paypal/done', array('payum_token' => $doneToken->getHash())) + ); + $tokenStorage->updateModel($doneToken); + + $captureToken = $tokenStorage->createModel(); + $captureToken->setPaymentName('paypal'); + $captureToken->setDetails($paymentDetailsStorage->getIdentificator($paymentDetails)); + $captureToken->setTargetUrl( + $this->createAbsoluteUrl('payment/capture', array('payum_token' => $captureToken->getHash())) + ); + $captureToken->setAfterUrl($doneToken->getTargetUrl()); + $tokenStorage->updateModel($captureToken); + + $paymentDetails['RETURNURL'] = $captureToken->getTargetUrl(); + $paymentDetails['CANCELURL'] = $captureToken->getTargetUrl(); + $paymentDetailsStorage->updateModel($paymentDetails); + + $this->redirect($captureToken->getTargetUrl()); + } + + public function actionDone() + { + $token = $this->getPayum()->getHttpRequestVerifier()->verify($_REQUEST); + $payment = $this->getPayum()->getRegistry()->getPayment($token->getPaymentName()); + + $payment->execute($status = new \Payum\Request\BinaryMaskStatusRequest($token)); + + $content = ''; + if ($status->isSuccess()) { + $content .= '

Payment status is success.

'; + } else { + $content .= '

Payment status IS NOT success.

'; + } + + $content .= '

'.json_encode(iterator_to_array($status->getModel()), JSON_PRETTY_PRINT); + + echo '
',$content,'
'; + exit; + } + + /** + * @return \Payum\YiiExtension\PayumComponent + */ + private function getPayum() + { + return Yii::app()->payum; + } +} +``` + +Back to [index](index.md). \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index e7f74ba..8743fca 100644 --- a/docs/index.md +++ b/docs/index.md @@ -2,3 +2,4 @@ * [Installation](installation.md) * [Get it started](get-it-started.md) +* [How to use active record storage](how-to-use-active-record-storage.md) diff --git a/src/Payum/YiiExtension/Model/PaymentActiveRecord.php b/src/Payum/YiiExtension/Model/PaymentActiveRecord.php new file mode 100644 index 0000000..ba76b59 --- /dev/null +++ b/src/Payum/YiiExtension/Model/PaymentActiveRecord.php @@ -0,0 +1,77 @@ +_hash = Random::generateToken(); + } */ + } + + /** + * @return string the associated database table name + */ + public function tableName() + { + return self::$_tableName; + } + + /** + * Returns the static model of the specified AR class. + * Please note that you should have this exact method in all your CActiveRecord descendants! + * @param string $tableName table corresponding to the model + * @param string $className active record class name. + * @return Payment the static model class + * @throws \Payum\Core\Exception\InvalidArgumentException + */ + public static function model($tableName, $className=__CLASS__) + { + if ($tableName == '') { + throw new InvalidArgumentException( + 'Table name must be supplied when trying to find a Payment' + ); + } + self::$_tableName = $tableName; + return parent::model($className); + } + +} diff --git a/src/Payum/YiiExtension/Model/PaymentDetails.php b/src/Payum/YiiExtension/Model/PaymentDetails.php index e996b0d..1f96ef2 100644 --- a/src/Payum/YiiExtension/Model/PaymentDetails.php +++ b/src/Payum/YiiExtension/Model/PaymentDetails.php @@ -3,7 +3,7 @@ class PaymentDetails extends \CActiveRecord { -/** + /** * @var array */ public $array = array(); diff --git a/src/Payum/YiiExtension/Model/PaymentDetailsActiveRecordWrapper.php b/src/Payum/YiiExtension/Model/PaymentDetailsActiveRecordWrapper.php new file mode 100644 index 0000000..172e0a9 --- /dev/null +++ b/src/Payum/YiiExtension/Model/PaymentDetailsActiveRecordWrapper.php @@ -0,0 +1,54 @@ + + * Date: 05/12/13 + * Time: 23:19 + */ + +namespace Payum\YiiExtension\Model; + +use Payum\Core\Model\ArrayObject; + +class PaymentDetailsActiveRecordWrapper extends ArrayObject +{ + public $activeRecord; + + public function __construct($scenario = 'insert', $tableName = '') + { + if ($scenario == 'insert') { + $this->activeRecord = new PaymentActiveRecord('insert', $tableName); + } + } + + public function primaryKey() + { + return $this->activeRecord->tableSchema->primaryKey; + } + + public function save() + { + $this->activeRecord->_details = serialize($this->array); + $this->activeRecord->save(); + $this[$this->primaryKey()] = $this->activeRecord->{$this->primaryKey()}; + } + + public function delete() + { + $this->activeRecord->delete(); + } + + public static function findModelById($tableName, $id) + { + $paymentDetails = new PaymentDetailsActiveRecordWrapper('update'); + $paymentDetails->activeRecord = PaymentActiveRecord::model($tableName)->findByPk($id); + $paymentDetails->refreshFromActiveRecord(); + + return $paymentDetails; + } + + public function refreshFromActiveRecord() + { + $this[$this->primaryKey()] = $this->activeRecord->{$this->primaryKey()}; + $this->array = unserialize($this->activeRecord->_details); + } +} diff --git a/src/Payum/YiiExtension/Model/PaymentSecurityToken.php b/src/Payum/YiiExtension/Model/PaymentSecurityToken.php index d7327de..df71cb3 100644 --- a/src/Payum/YiiExtension/Model/PaymentSecurityToken.php +++ b/src/Payum/YiiExtension/Model/PaymentSecurityToken.php @@ -1,151 +1,137 @@ + * Date: 04/12/13 + * Time: 18:22 + */ + namespace Payum\YiiExtension\Model; -use Payum\Security\TokenInterface; +use Payum\Core\Model\Token; -class PaymentSecurityToken extends \CActiveRecord implements TokenInterface +class PaymentSecurityToken extends Token { /** - * {@inheritDoc} + * @var \CActiveRecord */ - public function getDetails() - { - // TODO: Implement getDetails() method. - } + public $activeRecord; - /** - * {@inheritDoc} - */ - function setDetails($details) + public function __construct($scenario = 'insert', $tableName = '') { - // TODO: Implement setDetails() method. + if ($scenario == 'insert') { + $this->activeRecord = new TokenActiveRecord('insert', $tableName); + $this->hash = $this->activeRecord->_hash; + } } - /** - * {@inheritDoc} - */ - function getHash() + public function primaryKey() { - // TODO: Implement getHash() method. + return $this->activeRecord->primaryKey(); } - /** - * {@inheritDoc} - */ - function setHash($hash) + public function save() { - // TODO: Implement setHash() method. + $this->activeRecord->save(); } - /** - * {@inheritDoc} - */ - function getTargetUrl() + public function delete() { - // TODO: Implement getTargetUrl() method. + $this->activeRecord->delete(); } - /** - * {@inheritDoc} - */ - function setTargetUrl($targetUrl) + public static function findModelById($tableName, $id) { - // TODO: Implement setTargetUrl() method. - } + $token = new PaymentSecurityToken('update'); + $token->activeRecord = TokenActiveRecord::model($tableName)->findByPk($id); - /** - * {@inheritDoc} - */ - function getAfterUrl() - { - // TODO: Implement getAfterUrl() method. + // Load the values into the token object from the activeRecord + $token->hash = $token->activeRecord->_hash; + $token->targetUrl = $token->activeRecord->_target_url; + $token->afterUrl = $token->activeRecord->_after_url; + $token->paymentName = $token->activeRecord->_payment_name; + $token->details = $token->activeRecord->getDetailsIdentificator(); + return $token; } /** * {@inheritDoc} + * + * @return Identificator */ - function setAfterUrl($afterUrl) + public function getDetails() { - // TODO: Implement setAfterUrl() method. + return $this->details; } /** * {@inheritDoc} */ - function getPaymentName() + public function setDetails($details) { - // TODO: Implement getPaymentName() method. + $this->activeRecord->_details = $this->details = $details; } /** * {@inheritDoc} */ - function setPaymentName($paymentName) + public function getHash() { - // TODO: Implement setPaymentName() method. + return $this->hash; } /** - * @return string - */ - function getHash() - { - // TODO: Implement getHash() method. - } - - /** - * @param string $hash + * {@inheritDoc} */ - function setHash($hash) + public function setHash($hash) { - // TODO: Implement setHash() method. + $this->hash = $this->activeRecord->_hash = $hash; } /** - * @return string + * {@inheritDoc} */ - function getTargetUrl() + public function getTargetUrl() { - // TODO: Implement getTargetUrl() method. + return $this->targetUrl; } /** - * @param string $targetUrl + * {@inheritDoc} */ - function setTargetUrl($targetUrl) + public function setTargetUrl($targetUrl) { - // TODO: Implement setTargetUrl() method. + $this->targetUrl = $this->activeRecord->_target_url = $targetUrl; } /** - * @return string + * {@inheritDoc} */ - function getAfterUrl() + public function getAfterUrl() { - // TODO: Implement getAfterUrl() method. + return $this->afterUrl; } /** - * @param string $afterUrl + * {@inheritDoc} */ - function setAfterUrl($afterUrl) + public function setAfterUrl($afterUrl) { - // TODO: Implement setAfterUrl() method. + $this->afterUrl = $this->activeRecord->_after_url = $afterUrl; } /** - * @return string + * {@inheritDoc} */ - function getPaymentName() + public function getPaymentName() { - // TODO: Implement getPaymentName() method. + return $this->paymentName; } /** - * @param string $paymentName + * {@inheritDoc} */ - function setPaymentName($paymentName) + public function setPaymentName($paymentName) { - // TODO: Implement setPaymentName() method. + $this->paymentName = $this->activeRecord->_payment_name = $paymentName; } -} \ No newline at end of file +} diff --git a/src/Payum/YiiExtension/Model/TokenActiveRecord.php b/src/Payum/YiiExtension/Model/TokenActiveRecord.php new file mode 100644 index 0000000..738be42 --- /dev/null +++ b/src/Payum/YiiExtension/Model/TokenActiveRecord.php @@ -0,0 +1,174 @@ +_hash = Random::generateToken(); + } + } + + /** + * @return string the associated database table name + */ + public function tableName() + { + return self::$_tableName; + } + + /** + * Returns the static model of the specified AR class. + * Please note that you should have this exact method in all your CActiveRecord descendants! + * @param string $tableName table corresponding to the model + * @param string $className active record class name. + * @return Payment the static model class + * @throws \Payum\Core\Exception\InvalidArgumentException + */ + public static function model($tableName, $className=__CLASS__) + { + if ($tableName == '') { + throw new InvalidArgumentException( + 'Table name must be supplied when trying to find a PaymentSecurityToken' + ); + } + self::$_tableName = $tableName; + return parent::model($className); + } + + /** + * {@inheritDoc} + * @return Identificator + */ + public function getDetails() + { + $parts = explode("#", $this->_details); + return new Identificator($parts[1], $parts[0]); + } + + /** + * {@inheritDoc} + * + */ + function setDetails($details) + { + $this->_details = $details; + } + + /** + * {@inheritDoc} + */ + function getHash() + { + return $this->_hash; + } + + /** + * {@inheritDoc} + */ + function setHash($hash) + { + $this->_hash = $hash; + } + + /** + * {@inheritDoc} + */ + function getTargetUrl() + { + return $this->_target_url; + } + + /** + * {@inheritDoc} + */ + function setTargetUrl($targetUrl) + { + $this->_target_url = $targetUrl; + } + + /** + * {@inheritDoc} + */ + function getAfterUrl() + { + return $this->_after_url; + } + + /** + * {@inheritDoc} + */ + function setAfterUrl($afterUrl) + { + $this->_after_url = $afterUrl; + } + + /** + * {@inheritDoc} + */ + function getPaymentName() + { + return $this->_payment_name; + } + + /** + * {@inheritDoc} + */ + function setPaymentName($paymentName) + { + $this->_payment_name = $paymentName; + } + + /** + * @return \Payum\Core\Model\Identificator + */ + public function getDetailsIdentificator() + { + $parts = explode("#", $this->_details); + if (count($parts) == 2) { + return new Identificator($parts[1], $parts[0]); + } + return $this->_details; + } +} diff --git a/src/Payum/YiiExtension/PaymentController.php b/src/Payum/YiiExtension/PaymentController.php index 4ac9c44..4b520af 100644 --- a/src/Payum/YiiExtension/PaymentController.php +++ b/src/Payum/YiiExtension/PaymentController.php @@ -1,9 +1,9 @@ _tableName = $tableName; + } + + /** + * {@inheritDoc} + */ + public function createModel() + { + return new $this->modelClass('insert', $this->_tableName); + } + /** * {@inheritDoc} */ protected function doUpdateModel($model) { - $model::save(); + $model->save(); } /** @@ -29,11 +47,15 @@ protected function doDeleteModel($model) */ protected function doGetIdentificator($model) { - if (is_array($model->primaryKey()) { - throw new LogicException('Composite primary keys is not supported by this storage.'); + if (is_array($model->primaryKey())) { + throw new LogicException('Composite primary keys are not supported by this storage.'); } - return new Identificator($model->{$model->primaryKey()}, $this->modelClass); + if ($model instanceof ArrayObject) { + return new Identificator($model[$model->primaryKey()], $this->modelClass); + } else { + return new Identificator($model->{$model->primaryKey()}, $this->modelClass); + } } /** @@ -41,7 +63,8 @@ protected function doGetIdentificator($model) */ function findModelById($id) { - return $this->modelClass::model()->findByPk($id); + $className = $this->modelClass; + return $className::findModelById($this->_tableName, $id); } /** @@ -51,8 +74,11 @@ protected function assertModelSupported($model) { parent::assertModelSupported($model); - if (false == $model instanceof \CActiveRecord) { - throw new InvalidArgumentException('Invalid model given. Should be sub class of CActiveRecord class.'); + if (!property_exists(get_class($model), 'activeRecord') + || false == $model->activeRecord instanceof \CActiveRecord) { + throw new InvalidArgumentException( + 'Model required to have activeRecord property, which should be sub class of CActiveRecord class.' + ); } } -} \ No newline at end of file +}