Skip to content

Integration Guide: Payments for Marketplace

Đào Hoàng Sơn edited this page Dec 23, 2015 · 2 revisions

This guide includes code to integrate [bd] Paygates as payment methods for your Marketplace add-on or similar.

Introduction

To integrate payment methods, an add-on needs to do two things: rendering the payment forms (PayPal, Stripe, etc.) on a product page and handling callbacks (success, failure, etc.).

Render payment forms

/* @var $processorModel bdPaygate_Model_Processor */
$processorModel 
    = $this->getModelFromCache('bdPaygate_Model_Processor');

// get all available payment processors
$processorNames = $processorModel->getProcessorNames();
$processors = array();
foreach ($processorNames as $processorId => $processorClass) {
    $processors[$processorId] 
        = bdPaygate_Processor_Abstract::create($processorClass);
}

$visitor = XenForo_Visitor::getInstance();

// generate paygate-compatible item id
// $data is an array and it may any number of elements
// normally, you need to put in at least one internal id
// that can be used to know which product has been purchased
$data = array($product['product_id']);
$itemId = $processorModel->generateItemId('unique_action_code', $visitor, $data);

// change these variables if you do recurring payments
// $recurringUnit can be 'day', 'month' or 'year'
$recurringInterval = false;
$recurringUnit = false;

// the forms support some extra options you can make use of
$extraData = array();
// the most popular one is a return url after finishing the payment flow
// please note that user will be redirected there regardless of the transaction result
// because some processor takes time to verify their transaction
// so its result is not always immediately available
$extraData[bdPaygate_Processor_Abstract::EXTRA_RETURN_URL]
    = XenForo_Link::buildPublicLink('full:products/buy/thank-you', $product);

$paymentForms = bdPaygate_Processor_Abstract::prepareForms($processors,
    $product['amount'], $product['currency'],
    $product['product_name'], $itemId,
    $recurringInterval, $recurringUnit,
    $extraData
);

Handle callbacks

You need to extends bdPaygate_Model_Processor using XenForo code event listener and put something like below in it:

class AddOn_bdPaygate_Model_Processor extends XFCP_AddOn_bdPaygate_Model_Processor
{
    protected function _processIntegratedAction(
        $action, $user, $data,
        bdPaygate_Processor_Abstract $processor, $amount, $currency)
    {
        switch ($action) {
            case 'unique_action_code':
                // $data is exactly the same as you created above
                $productId = reset($data);
                $productModel = $this->getModelFromCache('AddOn_Model_Product');
                $product = $productModel->getProductById($productId);

                // return immediately if your code encounters any errors
                // the system will log everything for analysis later
                if (!$product) return sprintf('Product #%s not found', $productId);

                // some processors (but not all) report transferred amount, currency
                // you can use that information to optionally verify
                // whether user have purchased your product at the correct price
                // be careful if your marketplace offers discount, coupons etc.
                if ($amount !== false && $currency !== false) {
                    if ($amount - $product['amount'] > 0.001
                        || utf8_strtolower($currency) !== $product['currency']
                    ) {
                        return sprintf('Invalid purchase price of product #%d: %f %s (expected %f %s)',
                            $product['product_id'], $ammount, $currency,
                            $product['amount'], $product['currency']);
                    }
                }

                // if everything is good, update your product
                // $user is the full record of the purchaser
                $productModel->transferToUser($user);
                return sprintf('User #%d (%s) has bought product #%d (%s)',
                    $user['user_id'], $user['username'],
                    $product['product_id'], $product['product_name']);
        }

        // make sure you call the parent method to avoid future conflicts
        return parent::_processIntegratedAction($action, $user, $data, $processor, $amount, $currency);
    }

}