diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f19e98c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+/vendor/
+composer.lock
+.DS_Store
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..1a8c85f
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,10 @@
+# override like so: make dependencies COMPOSER=$(which composer.phar)
+COMPOSER = ./build/composer.phar
+
+.PHONY : test
+test:
+ php ./test/Affirm.php
+
+.PHONY : dependencies
+dependencies:
+ $(COMPOSER) update --dev
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..fed69cf
--- /dev/null
+++ b/README.md
@@ -0,0 +1,66 @@
+[](https://affirm.com)
+
+**Compatible with**
+
+Magento CE 1.4.0.1+
+
+Install
+-------
+
+**To install using [modgit](https://github.com/jreinke/modgit):**
+
+```
+cd MAGENTO_ROOT
+modgit -i extension/:. add Magento_Affirm git@github.com:Affirm/Magento_Affirm.git
+```
+to update:
+```
+modgit update Magento_Affirm
+```
+
+**To install using [modman](https://github.com/colinmollenhour/modman):**
+
+```
+cd MAGENTO_ROOT
+modman clone git@github.com:Affirm/Magento_Affirm.git
+```
+to update:
+```
+modman update Magento_Affirm
+```
+
+**To install using Affirm's deploy script:**
+
+1. Download the [Makefile](https://gist.githubusercontent.com/perfmode/ad8cf189bbbaeeae1181/raw/6fb5e861a6dddc8cd6685573e492b0662c498a15/Makefile) (requires wget)
+2. Copy to MAGENTO_ROOT
+3. To install, run `make install`
+4. To update, run `make update`
+
+Configure
+---------
+
+1. Log in to your Magento Admin portal.
+2. Visit System > Configuration > Payment Methods (under Sales) > Affirm
+3. Set the API URL. In a test environment, use ```https://sandbox.affirm.com```. On your live site, use ```https://www.affirm.com```.
+4. Provide your 3 keys (merchant API key, secret key, financial product key)
+5. Adjust the order total minimum and maximum options to control when Affirm is
+ shown to your customers.
+
+
+
+
+Contribute
+----------
+
+1. Fork the repo
+2. Create your feature branch (```git checkout -b my-new-feature```).
+3. Commit your changes (```git commit -am 'Added some feature'```)
+4. Push to the branch (```git push origin my-new-feature```)
+5. Create a Pull Request
+
+**To run the tests:**
+
+```
+make dependencies
+make test
+```
diff --git a/build/composer.phar b/build/composer.phar
new file mode 100755
index 0000000..3e369fa
Binary files /dev/null and b/build/composer.phar differ
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..7a04888
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,29 @@
+{
+ "name": "affirm/affirm-php",
+ "description": "Affirm PHP bindings",
+ "keywords": [
+ "affirm",
+ "api",
+ "consumer finance",
+ "credit",
+ "e-commerce",
+ "payment method"
+ ],
+ "homepage": "https://affirm.com/",
+ "authors": [
+ {
+ "name": "The Affirm Technology Team",
+ "homepage": "https://www.affirm.com/company"
+ }
+ ],
+ "require": {
+ "php": ">=5.2"
+ },
+ "require-dev": {
+ "zendframework/zendframework1": "1.12.6",
+ "simpletest/simpletest": "1.1.*"
+ },
+ "autoload": {
+ "classmap": ["extension/lib/Affirm/"]
+ }
+}
diff --git a/docs/config.png b/docs/config.png
new file mode 100644
index 0000000..adf828d
Binary files /dev/null and b/docs/config.png differ
diff --git a/docs/splash.png b/docs/splash.png
new file mode 100644
index 0000000..612b4db
Binary files /dev/null and b/docs/splash.png differ
diff --git a/extension/app/code/community/Affirm/Affirm/Block/Payment/Form.php b/extension/app/code/community/Affirm/Affirm/Block/Payment/Form.php
new file mode 100644
index 0000000..9bea8c1
--- /dev/null
+++ b/extension/app/code/community/Affirm/Affirm/Block/Payment/Form.php
@@ -0,0 +1,60 @@
+replaceLabel();
+ }
+
+ protected function _toHtml()
+ {
+ // TODO(brian): extract this html block to a template
+ // TODO(brian): extract css
+ $msg = "You'll complete your payment after you place your order.";
+
+ $html = "
";
+
+ return $html;
+ }
+
+ /* Replaces default label with custom image, conditionally displaying text
+ * based on the Affirm product.
+ *
+ * Context: Payment Information step of Checkout flow
+ */
+ private function replaceLabel()
+ {
+ $this->setMethodTitle(""); // removes default title
+
+ // TODO(brian): extract html to template
+ // TODO(brian): conditionally load based on env config option
+ // This is a stopgap until the promo API is ready to go
+ $logoSrc = "https://cdn1.affirm.com/images/badges/affirm-card_78x54.png";
+ $html = "
";
+
+ // TODO(brian): conditionally display based on payment type
+ // alt message: $html.= "Buy Now and Pay Later";
+ $html.= "3 Monthly Payments with Split Pay";
+
+ $this->setMethodLabelAfterHtml($html);
+ }
+}
diff --git a/extension/app/code/community/Affirm/Affirm/Block/Payment/Info.php b/extension/app/code/community/Affirm/Affirm/Block/Payment/Info.php
new file mode 100644
index 0000000..54c500e
--- /dev/null
+++ b/extension/app/code/community/Affirm/Affirm/Block/Payment/Info.php
@@ -0,0 +1,11 @@
+Affirm Split Pay';
+ return $html;
+ }
+
+}
diff --git a/extension/app/code/community/Affirm/Affirm/Block/Payment/Redirect.php b/extension/app/code/community/Affirm/Affirm/Block/Payment/Redirect.php
new file mode 100644
index 0000000..78425b9
--- /dev/null
+++ b/extension/app/code/community/Affirm/Affirm/Block/Payment/Redirect.php
@@ -0,0 +1,19 @@
+getOrder();
+ $payment_method = $order->getPayment()->getMethodInstance();
+
+ $html = '';
+ $html.= '';
+ $html.= '';
+ $html.= '';
+ return $html;
+ }
+}
diff --git a/extension/app/code/community/Affirm/Affirm/Helper/Data.php b/extension/app/code/community/Affirm/Affirm/Helper/Data.php
new file mode 100644
index 0000000..aa50654
--- /dev/null
+++ b/extension/app/code/community/Affirm/Affirm/Helper/Data.php
@@ -0,0 +1,6 @@
+getAcceptedCurrencyCodes())) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Return array of currency codes supplied by Payment Gateway
+ *
+ * @return array
+ */
+ public function getAcceptedCurrencyCodes()
+ {
+ if (!$this->hasData('_accepted_currency')) {
+ $acceptedCurrencyCodes = $this->_allowCurrencyCode;
+ $acceptedCurrencyCodes[] = $this->getConfigData('currency');
+ $this->setData('_accepted_currency', $acceptedCurrencyCodes);
+ }
+ return $this->_getData('_accepted_currency');
+ }
+
+ public function getChargeId()
+ {
+ return $this->getInfoInstance()->getAdditionalInformation("charge_id");
+ }
+
+ protected function setChargeId($charge_id)
+ {
+ return $this->getInfoInstance()->setAdditionalInformation("charge_id", $charge_id);
+ }
+
+ public function getBaseApiUrl()
+ {
+ return $this->getConfigData('api_url');
+ }
+
+ // TODO(brian): extract to a separate class and use DI to make it testable/mockable
+ public function _api_request($method, $path, $data=null)
+ {
+ $url = trim($this->getBaseApiUrl(), "/") . self::API_CHARGES_PATH . $path;
+
+ $client = new Zend_Http_Client($url);
+
+ if ($method == Zend_Http_Client::POST && $data)
+ {
+ $json = json_encode($data);
+ $client->setRawData($json, 'application/json');
+ }
+
+ $client->setAuth($this->getConfigData('api_key'), $this->getConfigData('secret_key'), Zend_Http_Client::AUTH_BASIC);
+
+ $raw_result = $client->request($method)->getRawBody();
+ try{
+ $ret_json = Zend_Json::decode($raw_result, Zend_Json::TYPE_ARRAY);
+ } catch(Zend_Json_Exception $e)
+ {
+ Mage::throwException(Mage::helper('affirm')->__('Invalid affirm response: '. $raw_result));
+ }
+
+ //validate to make sure there are no errors here
+ if (isset($ret_json["status_code"]))
+ {
+ Mage::throwException(Mage::helper('affirm')->__('Affirm error code:'. $ret_json["status_code"] . ' error: '. @$ret_json["message"]));
+ }
+ return $ret_json;
+ }
+
+ protected function _set_charge_result($result)
+ {
+ if (isset($result["id"]))
+ {
+ $this->setChargeId($result["id"]);
+ }
+ else
+ {
+ Mage::throwException(Mage::helper('affirm')->__('Affirm charge id not returned from call.'));
+ }
+ }
+
+ protected function _validate_amount_result($amount, $result)
+ {
+ if ($result["amount"] != $amount)
+ {
+ Mage::throwException(Mage::helper('affirm')->__('Affirm authorized amount of ' . $result["amount"].' does not match requested amount of: ' . $amount));
+ }
+ }
+
+ /**
+ * Send capture request to gateway
+ *
+ * @param Mage_Payment_Model_Info $payment
+ * @param decimal $amount
+ * @return Mage_Paygate_Model_Authorizenet
+ */
+ public function capture(Varien_Object $payment, $amount)
+ {
+ if ($amount <= 0) {
+ Mage::throwException(Mage::helper('affirm')->__('Invalid amount for capture.'));
+ }
+ $charge_id = $this->getChargeId();
+ $amount_cents = Affirm_Util::formatCents($amount);
+ if (!$charge_id) {
+ Mage::throwException(Mage::helper('affirm')->__('Charge id have not been set.'));
+ }
+ $result = $this->_api_request(Varien_Http_Client::POST, "{$charge_id}/capture");
+ $this->_validate_amount_result($amount_cents, $result);
+ return $this;
+ }
+
+ /**
+ * Refund capture
+ *
+ * @param Mage_Sales_Model_Order_Payment $payment
+ * @return Mage_Paypal_Model_Direct
+ */
+ public function refund(Varien_Object $payment, $amount)
+ {
+ if ($amount <= 0) {
+ Mage::throwException(Mage::helper('affirm')->__('Invalid amount for refund.'));
+ }
+ $charge_id = $this->getChargeId();
+ $amount_cents = Affirm_Util::formatCents($amount);
+ if (!$charge_id) {
+ Mage::throwException(Mage::helper('affirm')->__('Charge id have not been set.'));
+ }
+ $result = $this->_api_request(Varien_Http_Client::POST, "{$charge_id}/refund", array(
+ "amount"=>$amount_cents)
+ );
+ $this->_validate_amount_result($amount_cents, $result);
+
+ return $this;
+ }
+
+ public function void(Varien_Object $payment)
+ {
+ if (!$this->canVoid($payment)) {
+ Mage::throwException(Mage::helper('payment')->__('Void action is not available.'));
+ }
+ $charge_id = $this->getChargeId();
+ if (!$charge_id) {
+ Mage::throwException(Mage::helper('affirm')->__('Charge id have not been set.'));
+ }
+ $result = $this->_api_request(Varien_Http_Client::POST, "{$charge_id}/void");
+ return $this;
+ }
+
+ /**
+ * Send authorize request to gateway
+ *
+ * @param Mage_Payment_Model_Info $payment
+ * @param decimal $amount
+ * @return Mage_Paygate_Model_Authorizenet
+ */
+ public function authorize(Varien_Object $payment, $amount)
+ {
+ if ($amount <= 0) {
+ Mage::throwException(Mage::helper('affirm')->__('Invalid amount for authorization.'));
+ }
+
+ $amount_cents = Affirm_Util::formatCents($amount);
+ $token = $payment->getAdditionalInformation(self::CHECKOUT_TOKEN);
+
+ $result = $this->_api_request(Varien_Http_Client::POST, "", array(
+ self::CHECKOUT_TOKEN=>$token)
+ );
+
+ $this->_set_charge_result($result);
+ $this->_validate_amount_result($amount_cents, $result);
+ $payment->setTransactionId($this->getChargeId())->setIsTransactionClosed(0);
+ return $this;
+ }
+
+ /**
+ * Instantiate state and set it to state object
+ * @param string $paymentAction
+ * @param Varien_Object
+ */
+ public function initialize($paymentAction, $stateObject)
+ {
+ $state = Mage_Sales_Model_Order::STATE_PENDING_PAYMENT;
+ $stateObject->setState($state);
+ $stateObject->setStatus('pending_payment');
+ $stateObject->setIsNotified(false);
+ }
+
+
+ public function processConfirmOrder($order, $checkout_token)
+ {
+ $payment = $order->getPayment();
+
+ $payment->setAdditionalInformation(self::CHECKOUT_TOKEN, $checkout_token);
+ $action = $this->getConfigData('payment_action');
+
+ //authorize the total amount.
+ Affirm_Affirm_Model_Payment::authorizePaymentForOrder($payment, $order);
+ $payment->setAmountAuthorized(static::_affirmTotal($order));
+ $order->save();
+ //can capture as well..
+ if ($action == self::ACTION_AUTHORIZE_CAPTURE)
+ {
+ $payment->setAmountAuthorized(static::_affirmTotal($order));
+
+ // TODO(brian): It is unclear why this statement is here. If you
+ // know why, please replace this message with documentation to
+ // justify its existence.
+ $payment->setBaseAmountAuthorized($order->getBaseTotalDue());
+
+ $payment->capture(null);
+ $order->save();
+ }
+ }
+
+ /**
+ * Return Order place redirect url
+ *
+ * @return string
+ */
+ public function getOrderPlaceRedirectUrl()
+ {
+ return Mage::getUrl('affirm/payment/redirect', array('_secure' => true));
+ }
+
+ public function formatCents($currency, $amount)
+ {
+ return Affirm_Util::formatCents($amount);
+ }
+
+ public function getCheckoutObject($order)
+ {
+ $info = $this->getInfoInstance(); // TODO(brian): remove unused variable
+ $shipping_address = $order->getShippingAddress();
+ $shipping = null;
+ if ($shipping_address)
+ {
+ $shipping = array(
+ "name"=> array("full"=>$shipping_address->getName()),
+ "address"=> array(
+ "line1" => $shipping_address->getStreet(1),
+ "line2" => $shipping_address->getStreet(2),
+ "city" => $shipping_address->getCity(),
+ "state" => $shipping_address->getRegion(),
+ "country" => $shipping_address->getCountryModel()->getIso2Code(),
+ "zipcode" => $shipping_address->getPostcode(),
+ ));
+ }
+
+ $billing_address = $order->getBillingAddress();
+ $billing = array(
+ "email"=>$order->getCustomerEmail(),
+ "name"=> array("full"=>$billing_address->getName()),
+ "address"=> array(
+ "line1" => $billing_address->getStreet(1),
+ "line2" => $billing_address->getStreet(2),
+ "city" => $billing_address->getCity(),
+ "state" => $billing_address->getRegion(),
+ "country" => $billing_address->getCountryModel()->getIso2Code(),
+ "zipcode" => $billing_address->getPostcode(),
+ ));
+
+ $items = array();
+ $currency = $order->getOrderCurrency();
+ $products = Mage::getModel('catalog/product');
+ // TODO(brian): instantiate |pricer| upon construction
+ $pricer = Mage::getModel('affirm/pricer');
+ foreach($order->getAllVisibleItems() as $order_item)
+ {
+ $productId = $order_item->getProductId();
+ $product = $products->load($productId);
+
+ $items[] = array(
+ "sku" => $order_item->getSku(),
+ "display_name" => $order_item->getName(),
+ "item_url" => $product->getProductUrl(),
+ "item_image_url" => $product->getImageUrl(),
+ "qty" => intval($order_item->getQtyOrdered()),
+ "unit_price" => $pricer->getPriceInCents($order_item)
+ );
+ }
+
+ // TODO(brian): test checkout/onepage urls. it's unclear whether this
+ // is enabled for all merchants or whether merchant customization could
+ // cause this to be an invalid destination
+ $checkout = array(
+ 'checkout_id'=>$order->getIncrementId(),
+ 'currency'=>$order->getOrderCurrencyCode(),
+ 'shipping_amount'=>$this->formatCents($currency, $order->getShippingAmount()),
+ 'shipping_type'=>$order->getShippingMethod(),
+ 'tax_amount'=>$this->formatCents($currency, $order->getTaxAmount()),
+ "merchant" => array(
+ "public_api_key"=>$this->getConfigData('api_key'),
+ "user_confirmation_url"=>Mage::getUrl("affirm/payment/confirm"),
+ "user_cancel_url"=>Mage::helper('checkout/url')->getCheckoutUrl(),
+ "charge_declined_url"=>Mage::helper('checkout/url')->getCheckoutUrl()
+ ),
+ "config" => array("required_billing_fields"=> "name,address,email"),
+ "items" => $items,
+ "billing" => $billing);
+
+ // By convention, Affirm expects positive value for discount amount.
+ // Magento provides negative.
+ $discountAmtAffirm = -1 * $order->getDiscountAmount();
+ if ($discountAmtAffirm > 0.001)
+ {
+ $checkout["discounts"] = array(
+ $order->getCouponCode()=>array(
+ "discount_amount"=>$this->formatCents($currency, $discountAmtAffirm)
+ )
+ );
+ }
+
+ if ($shipping)
+ {
+ $checkout["shipping"] = $shipping;
+ }
+ $checkout['financial_product_key'] = $this->getConfigData('financial_product_key');
+
+ // TODO(brian): make this safer and less error-prone.
+ $checkout['total'] = Affirm_Util::formatCents(static::_affirmTotal($order));
+ $checkout['meta'] = static::_getMetadata();
+ return $checkout;
+ }
+
+ // TODO(brian): extract string name constant
+ private static function _getMetadata()
+ {
+ return array(
+ "source" => array(
+ "data" => array(
+ "is_logged_in" => Mage::getSingleton('customer/session')->isLoggedIn(),
+ "magento_version" => Mage::getVersion()
+ ),
+ "client_name" => "magento_affirm",
+ "version" => Mage::getConfig()->getModuleConfig('Affirm_Affirm')->version
+ )
+ );
+ }
+
+ /* A hacky thing used to access a private method (authorize(...)) on the
+ * payment object in order to provide compatibility with version 1.4.0.1 CE.
+ *
+ * FIXME(brian): take a closer look at the payment class at version 1.4.
+ * Surely, there _must_ be a way to accomplish this without reflection.
+ *
+ * TODO(brian): Write a regression test to catch incompatibilities with
+ * other Magento versions.
+ */
+ private static function authorizePaymentForOrder($payment, $order)
+ {
+ $moduleVersion = Mage::getConfig()->getModuleConfig("Mage_Sales")->version;
+ $incompatibleVersions = array(
+ "0.9.56"
+ );
+ if (in_array($moduleVersion, $incompatibleVersions)) {
+ Affirm_Affirm_Model_Payment::callPrivateMethod($payment, "_authorize", true, static::_affirmTotal($order));
+ } else {
+ $payment->authorize(true, static::_affirmTotal($order));
+ }
+ }
+
+ // TODO(brian): move this function to a helper library
+ private static function callPrivateMethod($object, $methodName)
+ {
+ $reflectionClass = new \ReflectionClass($object);
+ $reflectionMethod = $reflectionClass->getMethod($methodName);
+ $reflectionMethod->setAccessible(true);
+
+ $params = array_slice(func_get_args(), 2); //get all the parameters after $methodName
+ return $reflectionMethod->invokeArgs($object, $params);
+ }
+
+ // TODO(brian): move this to an external pricer so merchants can override
+ // the functionality.
+ private static function _affirmTotal($order)
+ {
+ return $order->getTotalDue();
+ }
+}
diff --git a/extension/app/code/community/Affirm/Affirm/Model/Pricer.php b/extension/app/code/community/Affirm/Affirm/Model/Pricer.php
new file mode 100644
index 0000000..8811fc3
--- /dev/null
+++ b/extension/app/code/community/Affirm/Affirm/Model/Pricer.php
@@ -0,0 +1,11 @@
+getPrice());
+ }
+}
diff --git a/extension/app/code/community/Affirm/Affirm/Model/Source/PaymentAction.php b/extension/app/code/community/Affirm/Affirm/Model/Source/PaymentAction.php
new file mode 100644
index 0000000..dc3f8dd
--- /dev/null
+++ b/extension/app/code/community/Affirm/Affirm/Model/Source/PaymentAction.php
@@ -0,0 +1,17 @@
+ Affirm_Affirm_Model_Payment::ACTION_AUTHORIZE,
+ 'label' => Mage::helper('affirm')->__('Authorize Only')
+ ),
+ array(
+ 'value' => Affirm_Affirm_Model_Payment::ACTION_AUTHORIZE_CAPTURE,
+ 'label' => Mage::helper('affirm')->__('Authorize and Capture')
+ ),
+ );
+ }
+}
diff --git a/extension/app/code/community/Affirm/Affirm/controllers/PaymentController.php b/extension/app/code/community/Affirm/Affirm/controllers/PaymentController.php
new file mode 100644
index 0000000..02568cf
--- /dev/null
+++ b/extension/app/code/community/Affirm/Affirm/controllers/PaymentController.php
@@ -0,0 +1,62 @@
+_quote) {
+ $this->_quote = $this->_getCheckoutSession()->getQuote();
+ }
+ return $this->_quote;
+ }
+
+ public function redirectAction()
+ {
+ $session = $this->_getCheckoutSession();
+ if (!$session->getLastRealOrderId())
+ {
+ $session->addError($this->__('Your order has expired.'));
+ $this->_redirect('checkout/cart');
+ return;
+ }
+ $order = Mage::getModel('sales/order')->loadByIncrementId($session->getLastRealOrderId());
+ $this->getResponse()->setBody($this->getLayout()->createBlock('affirm/payment_redirect')->setOrder($order)->toHtml());
+ $session->unsQuoteId();
+ $session->unsRedirectUrl();
+ }
+
+
+ public function confirmAction()
+ {
+ $session = $this->_getCheckoutSession();
+ $checkout_token = $this->getRequest()->getParam("checkout_token");
+ if (!$checkout_token)
+ {
+ Mage::throwException($this->__('Confirm has no checkout token.'));
+ }
+
+ if ($session->getLastRealOrderId()) {
+ $data = $this->getRequest()->getPost(); // TODO(brian): remove dead code
+ $order = Mage::getModel('sales/order')->loadByIncrementId($session->getLastRealOrderId());
+ $order->getPayment()->getMethodInstance()->processConfirmOrder($order, $checkout_token);
+
+ // TODO(brian): add a boolean configuration option to allow
+ // merchants to decide whether affirm should send emails upon email
+ // confirmation.
+ $order->sendNewOrderEmail();
+
+ $this->_redirect('checkout/onepage/success');
+ return;
+ }
+ $this->_redirect('checkout/onepage');
+ }
+
+ // TODO(brian): implement cancel action
+}
diff --git a/extension/app/code/community/Affirm/Affirm/etc/config.xml b/extension/app/code/community/Affirm/Affirm/etc/config.xml
new file mode 100644
index 0000000..dde0289
--- /dev/null
+++ b/extension/app/code/community/Affirm/Affirm/etc/config.xml
@@ -0,0 +1,55 @@
+
+
+
+
+ 0.3.1
+
+
+
+
+
+ Affirm_Affirm_Model
+
+
+
+
+ Affirm_Affirm_Block
+
+
+
+
+ Affirm_Affirm_Helper
+
+
+
+
+
+
+
+
+ Affirm_Affirm
+ affirm
+
+
+
+
+
+
+
+ 0
+ affirm/payment
+
+
+
+ authorize
+ https://www.affirm.com/
+
+
+ 1
+ 1
+ 1
+ USD
+
+
+
+
diff --git a/extension/app/code/community/Affirm/Affirm/etc/system.xml b/extension/app/code/community/Affirm/Affirm/etc/system.xml
new file mode 100644
index 0000000..0485057
--- /dev/null
+++ b/extension/app/code/community/Affirm/Affirm/etc/system.xml
@@ -0,0 +1,95 @@
+
+
+
+
+
+
+
+ text
+ 10
+ 1
+ 1
+ 1
+
+
+
+ select
+ affirm/source_paymentAction
+ 2
+ 1
+ 1
+ 0
+
+
+
+ select
+ adminhtml/system_config_source_yesno
+ 10
+ 1
+ 1
+ 0
+
+
+
+ text
+ 40
+ 1
+ 1
+ 0
+
+
+
+ text
+ 40
+ 1
+ 1
+ 0
+
+
+
+ obscure
+ adminhtml/system_config_backend_encrypted
+ 50
+ 1
+ 1
+ 0
+
+
+
+ text
+ 60
+ 1
+ 1
+ 0
+
+
+
+ select
+ adminhtml/system_config_source_currency
+ 100
+ 1
+ 1
+ 0
+
+
+
+ text
+ 180
+ 1
+ 1
+ 0
+
+
+
+ text
+ 190
+ 1
+ 1
+ 0
+
+
+
+
+
+
+
diff --git a/extension/app/etc/modules/Affirm_Affirm.xml b/extension/app/etc/modules/Affirm_Affirm.xml
new file mode 100644
index 0000000..a305c95
--- /dev/null
+++ b/extension/app/etc/modules/Affirm_Affirm.xml
@@ -0,0 +1,10 @@
+
+
+
+
+ true
+ community
+
+
+
+
diff --git a/extension/lib/Affirm/Affirm.php b/extension/lib/Affirm/Affirm.php
new file mode 100644
index 0000000..71dd160
--- /dev/null
+++ b/extension/lib/Affirm/Affirm.php
@@ -0,0 +1,4 @@
+, and either install it ".
+ "in your PHP include_path or put it in the test/ directory.\n";
+ exit(1);
+}
+
+require_once(dirname(__FILE__) . '/../extension/lib/Affirm/Affirm.php');
+
+require_once(dirname(__FILE__) . "/Affirm/UtilTest.php");
diff --git a/test/Affirm/UtilTest.php b/test/Affirm/UtilTest.php
new file mode 100644
index 0000000..6a38b98
--- /dev/null
+++ b/test/Affirm/UtilTest.php
@@ -0,0 +1,89 @@
+assertSame("10.45", Affirm_Util::formatMoney(10.45));
+ }
+
+ /* If implementation uses money_format to convert a float value to a string,
+ * the current locale will affect the result. This is a sanity check to keep
+ * the implementer honest.
+ */
+ public function testFormatMoneyNotAffectedByLocale() {
+ setlocale(LC_MONETARY, 'en_US');
+ $this->assertSame("10010.45", Affirm_Util::formatMoney(10010.45));
+
+ setlocale(LC_MONETARY, 'nl_NL');
+ $this->assertSame("10.45", Affirm_Util::formatMoney(10.45));
+
+ }
+
+ public function testFormatMoneyZero() {
+ $this->assertSame("0.00", Affirm_Util::formatMoney(0));
+ }
+
+ /*
+ * test formatCents
+ *
+ * Testing Methodology: Hit (1) each order of magnitude up to 10^5 cents making
+ * sure to hit (2) negative quantities and (3) quantities with trailing 0's.
+ *
+ * 10^5 is sufficient. An incorrect implementation could introduce ','
+ * comma's separating the hundreds and thousands places.
+ */
+
+ public function testFormatCentsEmpty() {
+ $this->assertSame(0, Affirm_Util::formatCents(null));
+ }
+
+ public function testFormatCentsBugsCaught() {
+ $floatAmount = 2299.99;
+ $naive = 229998;
+ $expected = 229999;
+ // NB: weird things happen to floats
+ $this->assertSame($naive, (int) (2299.99 * 100));
+ $this->assertSame($expected, Affirm_Util::formatCents(2299.99));
+ }
+
+ // 10^0
+ public function testFormatCentsOnes() {
+ $this->assertSame(0, Affirm_Util::formatCents(0));
+ $this->assertSame(7, Affirm_Util::formatCents(0.07));
+ $this->assertSame(-7, Affirm_Util::formatCents(-0.07));
+ }
+
+ // 10^1
+ public function testFormatCentsTens() {
+ $this->assertSame(10, Affirm_Util::formatCents(0.10));
+ $this->assertSame(12, Affirm_Util::formatCents(0.12));
+ $this->assertSame(-12, Affirm_Util::formatCents(-0.12));
+ }
+
+ // 10^2
+ public function testFormatCentsHundreds() {
+ $this->assertSame(120, Affirm_Util::formatCents(1.20));
+ $this->assertSame(123, Affirm_Util::formatCents(1.23));
+ $this->assertSame(-123, Affirm_Util::formatCents(-1.23));
+ }
+
+ // 10^3
+ public function testFormatCentsThousands() {
+ $this->assertSame(1230, Affirm_Util::formatCents(12.30));
+ $this->assertSame(1234, Affirm_Util::formatCents(12.34));
+ $this->assertSame(-1234, Affirm_Util::formatCents(-12.34));
+ }
+
+ // 10^4
+ public function testFormatCentsTensOfThousands() {
+ $this->assertSame(12340, Affirm_Util::formatCents(123.40));
+ $this->assertSame(12345, Affirm_Util::formatCents(123.45));
+ $this->assertSame(-12345, Affirm_Util::formatCents(-123.45));
+ }
+
+ // 10^5
+ public function testFormatCentsHundredThousands() {
+ $this->assertSame(123450, Affirm_Util::formatCents(1234.50));
+ $this->assertSame(123456, Affirm_Util::formatCents(1234.56));
+ $this->assertSame(-123456, Affirm_Util::formatCents(-1234.56));
+ }
+}
diff --git a/util/Makefile b/util/Makefile
new file mode 100644
index 0000000..797da6f
--- /dev/null
+++ b/util/Makefile
@@ -0,0 +1,38 @@
+# __ __ _
+# / _| / _|(_)
+# __ _ | |_ | |_ _ _ __ _ __ ___
+# / _` || _|| _|| || '__|| '_ ` _ \
+# | (_| || | | | | || | | | | | | |
+# \__,_||_| |_| |_||_| |_| |_| |_|
+#
+# Deploy script for Affirm's Payment Method Extension for the Magento eCommerce Platform
+# --------------------------------------------------------------------------------------
+#
+# Place this script in your Magento root and commit it to your version control system.
+#
+
+NAME = Magento_Affirm
+
+REPO = git@github.com:Affirm/Magento_Affirm.git
+BRANCH = master
+
+SRC = extension/
+DEST = .
+
+SCRIPT = aff-modgit
+CMD = ./$(SCRIPT)
+
+.modgit: $(SCRIPT)
+ $(CMD) init
+
+install: $(SCRIPT)
+ $(CMD) -i $(SRC):$(DEST) -b $(BRANCH) add $(NAME) $(REPO)
+
+update: $(SCRIPT)
+ $(CMD) up $(NAME)
+
+remove: $(SCRIPT)
+ $(CMD) rm $(NAME)
+
+$(SCRIPT):
+ wget -O $(SCRIPT) https://raw.githubusercontent.com/Affirm/modgit/master/modgit