Skip to content

Instantly share code, notes, and snippets.

@frankmullenger
Created May 17, 2012 06:12
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save frankmullenger/2716899 to your computer and use it in GitHub Desktop.
Save frankmullenger/2716899 to your computer and use it in GitHub Desktop.
Payment Module Outlines
<?php
/*
* Payment
* ====================================================
*/
Payment::set_supported_methods(array(
'ChequePayment' => 'Cheque Or Pay On Site',
'PayPalPayment' => 'Pay Pal'
));
if (Director::isLive()) {
Payment_Controller::set_environment('live');
//TODO set PayPalPayment live API details
}
else if (Director::isDev()) {
Payment_Controller::set_environment('dev');
//TODO set PayPalPayment sandbox account API details
}
else if (strpos($_SERVER['REQUEST_URI'], '/dev/tests') !== false) { //Unit testing
Payment_Controller::set_environment('test');
}
<?php
class OrderPage extends Page {
}
/**
* Represents a page with a form which a user fills out to process payment
*/
class OrderPage_Controller extends Page_Controller {
function OrderForm() {
$fields = new FieldList();
//Get form fields for payment methods
$paymentFields = Payment::combined_form_fields(); //Perhaps a nicer way of doing this?
$fields->push($paymentFields);
$actions = new FieldList(
new FormAction('ProcessOrder', 'Proceed to pay')
);
$validator = new OrderFormValidator();
return new Form($this, 'OrderForm', $fields, $actions, $validator);
}
function ProcessOrder($data, $form) {
//What is the payment method? Find out from $data?
//What is the environment? How to choose Production / Sandbox / Mock? Factory pattern appropriate?
//$payment = new PayPalPayment_Gateway();
//$paymentController = new PayPalPayment_Controller_Sandbox($payment);
$paymentClassName = $data['PaymentMethod'];
$paymentController = new PaymentFactory($paymentClassName);
$paymentProcessor = new PaymentProcessor($paymentController);
$result = $paymentProcessor->processPayment($data);
//If instant payment success
if ($result->isSuccess()) {
}
//If payment is being processed
//e.g gateway payment process redirected to another website (PayPal, DPS)
if ($result->isProcessing()) {
}
//If payment failed
if (!$result->isSuccess() && !$result->isProcessing()) {
}
}
}
<?php
class Payment {
const STATUS_SUCCESS = 1;
const STATUS_PROCESSING = 0;
const STATUS_FAILURE = -1;
/**
* Both Member and Object are optional.
* Object to represent object being paid for e.g: an order
*
* @var Array
*/
public static $has_one = array(
'Member' => 'Member',
'Object' => 'Object'
);
}
class Payment_Controller extends Controller {
private static $environment;
/**
* Inject Payment
*
* @param Payment $payment
*/
public function __construct(Payment $payment = null) {
//DI here
}
public static function set_environment(String $env) {
//Bounds checking on $env - one of: live / dev / test
//similar to Director::set_environment_type()
self::$environment = $env;
}
public static function environment() {
return self::$environment;
}
}
interface Payment_Controller_Interface {
public function getFormFields();
public function getFormRequirements();
public function processRequest();
public function processResponse();
}
<?php
class PaymentFactory {
/**
* Map the environment to controller naming convention
*
* @see Director::set_environment_type()
* @var Array
*/
private $envMapping = array(
'live' => '_Production',
'dev' => '_Sandbox',
'test' => '_Mock'
);
/**
* Create payment controller with payment injected into it.
*
* @param String $className Payment class name
* @param String $environment Environment
* @return Payment_Controller
*/
public function __construct(String $paymentClassName, String $environment = null) {
//Create Payment class from className, bounds check that $className is typeof Payment
$payment = class_exists($paymentClassName) ? new $paymentClassName() : null;
//TODO the code below to create the Payment_Controller is a bit nasty, and while it would work for the circumstance:
//PayPalPayment has controller PayPalPayment_Controller_(Production/Sandbox/Mock)
//the circumstance:
//PayPalPayment_Gateway has controller PayPalPayment_Controller_(Production/Sandbox/Mock)
//is a bit of an unusual binding.
//
//We could use a convention like this but also provide a way to set the name of the corresponding Payment_Controller
//class in the Payment class so that strict naming conventions do not need to be adhered to.
//Create Payment_Controller from className and environment
if (!$environment) {
$environment = PaymentController::environment();
}
$controllerSuffix = self::$envMapping[$environment];
//TODO use mapping for env to controller naming convention to create controller class
$paymentControllerClassName = $className . '_Controller' . $controllerSuffix;
$paymentController = new $paymentControllerClassName($payment);
}
}
<?php
class PaymentProcessor {
public function __construct(Payment_Controller $paymentController) {
//DI here
}
/**
* Process payment using injected payment controller.
*
* @param Array $data Form data
* @return PaymentProcessor_Result
*/
function processPayment(Array $data) {
$payment = $this->paymentController->processRequest($data);
if ($payment->isSuccess()) {
return new PaymentProcessor_Success;
}
if ($payment->isProcessing()) {
return new PaymentProcessor_Processing;
}
if ($payment->isFailed()) {
return new PaymentProcessor_Failure;
}
}
}
abstract class PaymentProcessor_Result {
protected $value;
function __construct($value = null) {
$this->value = $value;
}
function getValue() {
return $this->value;
}
abstract function isSuccess();
abstract function isProcessing();
}
class PaymentProcessor_Success extends PaymentProcessor_Result {
function isSuccess() {
return true;
}
function isProcessing() {
return false;
}
}
class PaymentProcessor_Processing extends PaymentProcessor_Result {
function isSuccess() {
return false;
}
function isProcessing() {
return true;
}
}
class PaymentProcessor_Failure extends PaymentProcessor_Result {
function isSuccess() {
return false;
}
function isProcessing() {
return false;
}
}
<?php
class PayPalPayment extends Payment {
}
class PayPalPayment_Gateway extends PayPalPayment {
/**
* Gets fields for processing via gateway e.g: fields required for PayPal API
*
* @return FieldList
*/
public function getFormFields() {
}
public function getFormRequirements() {
}
}
class PayPalPayment_Merchant extends PayPalPayment {
/**
* Gets fields for processing credit card on site e.g: CreditCardField, month, year, cvv fields
*
* @return FieldList
*/
public function getFormFields() {
}
public function getFormRequirements() {
}
}
class PayPalPayment_Controller extends Payment_Controller implements Payment_Controller_Interface {
public function getFormFields() {
return $this->Payment->getFormFields();
}
/**
* Getting required fields, perhaps there is a better approach?
* Need to set rules for Validator on OrderPage::OrderForm()
* Maybe wait until form validation GSOC work completed?
*/
public function getFormRequirements() {
}
public function processRequest() {
}
public function processResponse() {
}
}
class PayPalPayment_Controller_Production extends Payment_Controller {
/**
* Not sure what to return here
*
* @param Array $data
* @return Payment
*/
public function processRequest(Array $data) {
if ($this->Payment instanceof PayPalPayment_Gateway) {
//send data to API with production account details
//save Payment with status 'processing'
}
if ($this->Payment instanceof PayPalPayment_Merchant) {
//save data to DB
}
//Maybe return Payment object?
return $this->Payment;
}
public function processResponse() {
}
}
class PayPalPayment_Controller_Sandbox extends Payment_Controller {
/**
* Probably pretty much identical to PayPalPayment_Controller_Production::processRequest()
* TODO find a way to keep this DRY - shared parent class?
*/
public function processRequest(Array $data) {
//If payment gateway, send data to API with sandbox account details
//If payment merchant, save data
}
public function processResponse() {
}
}
class PayPalPayment_Controller_Mock extends Payment_Controller {
public function processRequest(Array $data) {
//If payment gateway, redirect to processResponse() with mocked HTTP response from gateway
//If payment merchant, save data
}
public function processResponse() {
}
}
@frankmullenger
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment