Nick Coons
Nick Coons

Reputation: 3692

Building Classes to Abstract Functionality of API

I'm looking at building a set of classes to handle interactions with various similar but competing APIs. A good illustration of this would be for credit card processing. I want a class with methods like charge() or refund() that the application developer uses, independent of who the merchant processor is. Then I want to be able to build classes that handle the interaction with the specific merchant processor API.

So I might have one that interacts with the Stripe API, another for Authorize.Net, etc. Then some sort of master or wrapper class that abstracts the API specifics from the application developer.

In the past, I've done this with a wrapper class, where I created a class for each API using the same methods (with their respective API interactions), and then a wrapper class that is used in the application. A usage example might look like this:

$merchant = new Merchant( 'Stripe' );
$merchant->set_credentials( 'api_user', 'api_password' );
$merchant->set_cc( '4111-1111-1111-1111' );
$merchant->set_exp( '0121' );
$merchant->set_amount( 100.00 );
$merchant->charge();

Instantiating this class with the value "Stripe" would mean that behind the scenes this class is passing the workload off to the appropriate class to handle this interaction.

My goals are to:

Is a wrapper class the way to do this, or does PHP provide other more efficient mechanisms for handling this type of setup?

Upvotes: 0

Views: 113

Answers (1)

Arno Hilke
Arno Hilke

Reputation: 1139

I would create an interface for your internal API, one implementation per external processor, and a factory class to create the right instance.

Code Example (PHP 7.1):

interface MerchantInterface {
    public function set_credentials(string $username, string $password);
    public function set_cc(string $number);
    public function set_exp(string $number);
    public function set_amount(float $amount);
    public function charge();
}

class StripeMerchant implements MerchantInterface {
    public function set_credentials(string $username, string $password) {}
    public function set_cc(string $number)  {}
    public function set_exp(string $number) {}
    public function set_amount(float $amount) {}
    public function charge() {}
}

class AuthorizeNetMerchant implements MerchantInterface {
    public function set_credentials(string $username, string $password) {}
    public function set_cc(string $number)  {}
    public function set_exp(string $number) {}
    public function set_amount(float $amount) {}
    public function charge() {}
}

class MerchantFactory {
    public const MERCHANT_STRIPE = 'Stripe';
    public const MERCHANT_AUTHORIZE_NET = 'Authorize.Net';

    public static function create(string $merchant): MerchantInterface {
        switch ($merchant) {
            case self::MERCHANT_STRIPE:
                return new StripeMerchant();
            case self::MERCHANT_AUTHORIZE_NET:
                return new AuthorizeNetMerchant();
            default:
                throw new Exception('Unexpected Merchant');
        }
    }
}

$stripeMerchant = MerchantFactory::create(MerchantFactory::MERCHANT_STRIPE);
$authorizeNetMerchant = MerchantFactory::create(MerchantFactory::MERCHANT_AUTHORIZE_NET);

Depending on your requirements, you could also use the builder pattern instead of a factory to create your different instances. The builder would take care of your setters. That might be useful if you have many optional parameters (does not seem to be the case here) or you want to make your Merchants immutable.

Upvotes: 1

Related Questions