Skip to content

Instantly share code, notes, and snippets.

@cleggypdc
Created Sep 6, 2021
Embed
What would you like to do?
Pimcore Stripe Payment Provider (From Pimcore4 needs porting to bundle)
<?php
/**
* Service.php
*
* This source file is subject to the GNU General Public License version 3 (GPLv3)
* For the full copyright and license information, please view the LICENSE.md
* file distributed with this source code.
*
* @copyright Copyright (c) 2012-2021 Gather Digital Ltd (https://www.gatherdigital.co.uk)
* @license https://www.gatherdigital.co.uk/license GNU General Public License version 3 (GPLv3)
* @author Paul Clegg
*/
namespace StripePaymentProvider;
use StripePaymentProvider\Interfaces\ICustomer;
use StripePaymentProvider\Interfaces\IStripePlan;
use \OnlineShop\Framework\PriceSystem\IPrice;
use \Pimcore\Log;
use \Stripe as StripeLib;
class Service
{
/**
* @var $payment StripePaymentProvider
*/
private $payment;
private $logger;
public function __construct(StripePaymentProvider $payment)
{
$this->payment = $payment;
$this->logger = Log\ApplicationLogger::getInstance('stripe-payment-service', true);
//init the API
StripeLib\Stripe::setApiKey($this->payment->getCurrentApiConfig()->secretKey);
}
public function translateTokenToStripeChargeDetail($stripeToken, $customer)
{
// check for customers with same stripe config
$stripeCustomerRef = $customer->getStripeCustomer($this->payment->getMode(), $this->payment->getStripeAccountName());
if (!$stripeCustomerRef) {
// create a new customer for this user (uses the token)
$stripeCustomer = $this->createCustomer($stripeToken, $customer->getEmail());
$customer->addStripeCustomer($this->payment->getMode(), $this->payment->getStripeAccountName(), $stripeCustomer, $stripeToken);
$customer->save();
return ['stripeCustomerId' => $stripeCustomer->id, 'stripePaymentSource' => null, 'customerId' => $customer->getId()];
}
// callback to handle setting the default card
$stripeCustomer = $this->retrieveCustomer($stripeCustomerRef); //the customer ID
// if stripe token is actually a string , we passed a card id to initPayment - so the user has opted to use their default card
if (is_string($stripeToken)) {
return ['stripeCustomerId' => $stripeCustomer->id, 'stripePaymentSource' => $stripeToken, 'customerId' => $customer->getId()];
}
$isSubscription = $this->payment->getAuthorizedData()['paymentType'] == 'subscription';
$checkShouldUpdateDefaultCard = function(&$customer, &$token)
use ($isSubscription) {
if ($isSubscription) {
// force set default source to the latest one the user added
$cards = $customer->sources->all(['object' => 'card']);
foreach ($cards->data as $card) {
if ($card->fingerprint === $token->card->fingerprint) {
$customer->default_source = $card->id;
$customer->save();
}
}
}
};
//check if the card exists in customer account.
/**
* @var $cards StripeLib\Collection
*/
$match=false;
$cards = $stripeCustomer->sources->all(["object" => "card"]);
foreach ($cards->data as $source) {
if ($source->fingerprint == $stripeToken->card->fingerprint) {
$match = $source->id;
}
}
// if the card matches the card used in the customer account then return the customer with the token ID
if ($match) {
$checkShouldUpdateDefaultCard($stripeCustomer, $stripeToken);
return ['stripeCustomerId' => $stripeCustomer->id, 'stripePaymentSource' => $match, 'customerId' => $customer->getId()];
}
// if there isn't a card saved with the correct id, then we should have the token ready to use to add a new card
// otherwise we have some token problems
if ($stripeToken->used) {
throw new \Exception('Token has been used, card detail retrieval error');
}
// create a new card for the customer
$newCard = $stripeCustomer->sources->create(['source' => $stripeToken->id]);
if (!$newCard) {
throw new \Exception('Could not create a new card');
}
$checkShouldUpdateDefaultCard($stripeCustomer, $stripeToken);
return ['stripeCustomerId' => $stripeCustomer->id, 'stripePaymentSource' => $newCard->id, 'customerId' => $customer->getId()];
}
/**
* Returns a plan for this recurring payment
* @param $authData
* @return int
*/
public function translatePaymentAuthDataToStripePlan($authData, IPrice $price, $items)
{
/**
* @var $className IStripePlan|\Pimcore\Model\Object\Concrete
*/
$className = $this->payment->stripePlanClassName;
// check for already existing config
if (!empty($authData['planId']) && $className::getById($authData['planId'])) {
return $authData['planId'];
}
// create a new plan if needed
$intervalCount = (int)($authData['intervalCount'] ?: 1);
// determine the plan ID from the auth data
$stripePlanId = Tool::getValidStripePlanId($price, $authData['interval'], $intervalCount, $this->payment);
$planName = Tool::getValidStripePlanName($price, $authData['interval'], $intervalCount, $this->payment);
$amount = Tool::getStripeAmount($price);
$currency = $price->getCurrency()->getShortName();
$description = '';
$adopt = '';
$itemName = [];
if ($items) {
foreach ($items as $item) {
if ($item->getProduct()->getProductType() === \Website\DefaultProduct::PRODUCT_TYPE_DONATION) {
$description = $item->getProduct()->getName();
} else if ($item->getProduct()->getProductType() === \Website\DefaultProduct::PRODUCT_TYPE_ADOPTION) {
$adopt = 'Adopt';
$itemName[] = $item->getProduct()->getName();
}
}
if ($adopt) {
$description = $adopt . ' ' . implode(",", $itemName) . ' ' . $item->getProduct()->getSubscriptionType() . 'ly';
}
}
// check if the plan exists in Stripe already.
$stripePlan = $this->tryApiCall('retrieve-plan', [
'id' => $stripePlanId
]);
// if it doesn't exist then create a new one.
if (!$stripePlan instanceof StripeLib\Plan) {
$stripePlan = $this->tryApiCall('create-plan', [
'id' => $stripePlanId,
'amount' => $amount,
'currency' => $currency,
'interval' => $authData['interval'],
'name' => $planName,
/* 'nickname' => $description, */ // TODO: this is not a valid param. /some idiot/ added it without testing or reading the documentation and assumed it would work - will be fixed in a later update
'interval_count' => $intervalCount
]);
}
if (!$stripePlan instanceof StripeLib\Plan) {
\Pimcore\Logger::crit('Could not create a stripe plan' . print_r([
$stripePlanId
], true));
throw new StripePaymentException('The payment provider could not create a plan for your subscription');
}
// now check to make sure that the plan exists in Pimcore
$plan = $className::getByStripeIdAndApiStatus(
$stripePlanId,
$this->payment->getStripeAccountName(), $this->payment->isLivemode()
);
// or create one since no plan exists
if (!$plan) {
$this->createNewLocalStripePlan([
'name' => $planName,
'stripeId' => $stripePlan->id,
'stripeAccount' => $this->payment->getStripeAccountName(),
'currency' => $currency,
'amount' => $amount,
'interval' => $authData['interval'],
'intervalCount' => $intervalCount,
'livemode' => $stripePlan->livemode
]);
}
return $stripePlan->id;
}
/**
* Provides an implementation of API calls that are retried in case of any network or API issues.
*
* @param string $call
* @param mixed $params
* @param string $idempotencyKey
* @param int $try
* @return \Stripe\ApiResource|\Exception|string
* @throws \Exception
*/
public function tryApiCall($call, $params, $idempotencyKey = null, $try = 1)
{
// check API key is setup first
if (!StripeLib\Stripe::getApiKey()) {
throw new \Exception('Could not initialise API without first setting the API key');
}
// logging (anonymize emails and descriptions)
try {
$anonLog = array_filter($params, function ($value, $key) {
return !in_array(key, ['email', 'description']);
}, ARRAY_FILTER_USE_BOTH);
$this->logger->debug(sprintf(
'API: %s [params=%s, try=%d, scope=%s]', $call, json_encode($anonLog), $try,
$this->payment->getStripeAccountName()
));
} catch(\Throwable $e) {
// in case of any application logging problems still allow the payment to continue.
}
if (!$idempotencyKey) {
$idempotencyKey = Tool::getIdempotencyKey();
}
$options = [
"idempotency_key" => $idempotencyKey
];
//call stripe API
try {
if ($try > 4) {
$this->logger->error('API: Cannot process request (too many failed api attempts)');
return 'Cannot process the request at this time.';
}
switch($call) {
case 'charge' :
$result = StripeLib\Charge::create($params, $options);
break;
case 'retrieve-charge' :
$result = StripeLib\Charge::retrieve($params, $options);
break;
case 'retrieve-customer-charge' :
$result = StripeLib\Charge::all($params, $options);
break;
case 'retrieve-token' :
$result = StripeLib\Token::retrieve($params, $options);
break;
case 'create-customer' :
$result = StripeLib\Customer::create($params, $options);
break;
case 'retrieve-customer' :
$result = StripeLib\Customer::retrieve($params, $options);
break;
case 'create-plan' :
$result = StripeLib\Plan::create($params, $options);
break;
case 'retrieve-plan':
$result = StripeLib\Plan::retrieve($params, $options);
break;
case 'create-subscription' :
$result = StripeLib\Subscription::create($params, $options);
break;
case 'retrieve-subscription' :
$result = StripeLib\Subscription::retrieve($params, $options);
break;
case 'retrieve-customer-subscription' :
$result = StripeLib\Subscription::all($params, $options);
break;
case 'retrieve-invoice' :
$result = StripeLib\Invoice::retrieve($params, $options);
break;
case 'retrieve-customer-invoice' :
$result = StripeLib\Invoice::all($params, $options);
break;
default:
$result = new \Exception("Unspecified API call '{$call}'");
break;
}
return $result;
} catch (StripeLib\Error\Card $e) {
$reason = $this->determineDeclineReason($e->getDeclineCode());
$this->logger->info(sprintf('Card Declined: %s' . $reason));
return 'Card Declined. ' . $reason; //card declined
} catch (StripeLib\Error\RateLimit $e) {
// Too many requests made to the API too quickly
sleep(pow($try, 2));
return $this->tryApiCall($call, $params, null, $try + 1);
} catch (StripeLib\Error\InvalidRequest $e) {
$this->logger->error('InvalidRequest '.print_r($params,true));
// Invalid parameters were supplied to Stripe's API
return $e;
} catch (StripeLib\Error\Authentication $e) {
// Authentication with Stripe's API failed
// (maybe you changed API keys recently)
$this->logger->error('AuthError '.print_r($params,true));
return $e;
} catch (StripeLib\Error\ApiConnection $e) {
// Network communication with Stripe failed
return $this->tryApiCall($call, $params, $idempotencyKey, $try + 1);
} catch (StripeLib\Error\Base $e) {
// Display a very generic error to the user, and maybe send
// yourself an email
$this->logger->error('Stripe API Base error' . $e->getMessage());
return 'Our payment provider is currently having problems, please try again later';
} catch (\Exception $e) {
$this->logger->error('GenericException '.print_r($params,true));
return $e;
}
}
/**
* @param StripeLib\Token $token
* @param string $email
* @param string $stripeAccount
* @return StripeLib\Customer
* @throws \Exception
*/
public function createCustomer(StripeLib\Token $token, $email)
{
$stripeAccount = $this->payment->getStripeAccountName();
// first attempt to make a new stripe customer
$stripeCustomer = $this->tryApiCall('create-customer', [
"source" => $token->id,
"email" => $email,
"description" => "Customer for {$email} -> {$stripeAccount}"
]);
if (!$stripeCustomer instanceof StripeLib\Customer) {
$message = (($stripeCustomer instanceof \Exception) ? $stripeCustomer->getMessage() : $stripeCustomer);
\Pimcore\Logger::crit('Could not create a customer' . print_r([
$token->jsonSerialize(),
$email,
$stripeAccount,
$message
], true));
throw new StripePaymentException($message);
}
if (!$stripeCustomer->default_source) {
\Pimcore\Logger::crit('Could not create a customer with a default payment method' . print_r([
$stripeCustomer->id,
$token->jsonSerialize(),
$email,
$stripeAccount
], true));
throw new StripePaymentException('The payment provider could not verify customer payment details');
}
return $stripeCustomer;
}
public function retrieveCustomer($customerId)
{
$stripeAccount = $this->payment->getStripeAccountName();
$stripeCustomer = $this->tryApiCall('retrieve-customer', [
'id' => $customerId
]);
if (!$stripeCustomer instanceof StripeLib\Customer) {
$message = (($stripeCustomer instanceof \Exception) ? $stripeCustomer->getMessage() : $stripeCustomer);
\Pimcore\Logger::crit('Could not retreive existing customer' . print_r([
$customerId,
$stripeAccount,
$message
], true));
throw new StripePaymentException($message);
}
return $stripeCustomer;
}
/**
* @param \OnlineShop\Framework\PriceSystem\IPrice $price
* @param mixed $authData
* @param int $intervalCount
* @return IStripePlan
* @throws \Exception
*/
public function createNewLocalStripePlan($values)
{
// create the local stripe plan
$classname = $this->payment->stripePlanClassName;
/**
* @var IStripePlan|\Pimcore\Model\Object\Concrete $localStripePlan
*/
$localStripePlan = new $classname();
$localStripePlan->setValues($values);
$localStripePlan->generateOwnKey();
$localStripePlan->generateOwnParent();
$localStripePlan->setPublished(true);
$localStripePlan->setOmitMandatoryCheck(true);
$localStripePlan->save();
return $localStripePlan;
}
/**
* @param $id
* @return IStripePlan
*/
public function getStripePlan($id)
{
/**
* @var $classname \Pimcore\Model\Object\Concrete|IStripePlan
*/
$classname = $this->payment->stripePlanClassName;
return $classname::getById($id);
}
public function getPayment(){
return $this->payment;
}
public function determineDeclineReason($declineCode='')
{
$reasons = [
"approve_with_id" => "The payment cannot be authorized.",
"call_issuer" => "The card has been declined for an unknown reason.",
"card_not_supported" => "The card does not support this type of purchase.",
"card_velocity_exceeded" => "The customer has exceeded the balance or credit limit available on their card.",
"currency_not_supported" => "The card does not support the specified currency.",
"do_not_honor" => "The card has been declined for an unknown reason.",
"do_not_try_again" => "The card has been declined for an unknown reason.",
"duplicate_transaction" => "A transaction with identical amount and credit card information was submitted very recently.",
"expired_card" => "The card has expired.",
"fraudulent" => "The payment has been declined as Stripe suspects it is fraudulent.",
"generic_decline" => "The card has been declined for an unknown reason.",
"incorrect_number" => "The card number is incorrect.",
"incorrect_cvc" => "The CVC number is incorrect.",
"incorrect_pin" => "The PIN entered is incorrect. This decline code only applies to payments made with a card reader.",
"incorrect_zip" => "The ZIP/postal code is incorrect.",
"insufficient_funds" => "The card has insufficient funds to complete the purchase.",
"invalid_account" => "The card, or account the card is connected to, is invalid.",
"invalid_amount" => "The payment amount is invalid, or exceeds the amount that is allowed.",
"invalid_cvc" => "The CVC number is incorrect.",
"invalid_expiry_year" => "The expiration year invalid.",
"invalid_number" => "The card number is incorrect.",
"invalid_pin" => "The PIN entered is incorrect. This decline code only applies to payments made with a card reader.",
"issuer_not_available" => "The card issuer could not be reached, so the payment could not be authorized.",
"lost_card" => "The payment has been declined because the card is reported lost.",
"new_account_information_available" => "The card, or account the card is connected to, is invalid.",
"no_action_taken" => "The card has been declined for an unknown reason.",
"not_permitted" => "The payment is not permitted.",
"pickup_card" => "The card cannot be used to make th,is payment (it is possible it has been reported lost or stolen).",
"pin_try_exceeded" => "The allowable number of PIN tries has been exceeded.",
"processing_error" => "An error occurred while processing the card.",
"reenter_transaction" => "The payment could not be processed by the issuer for an unknown reason.",
"restricted_card" => "The card cannot be used to make this payment (it is possible it has been reported lost or stolen).",
"revocation_of_all_authorizations" => "The card has been declined for an unknown reason.",
"revocation_of_authorization" => "The card has been declined for an unknown reason.",
"security_violation" => "The card has been declined for an unknown reason.",
"service_not_allowed" => "The card has been declined for an unknown reason.",
"stolen_card" => "The payment has been declined because the card is reported stolen.",
"stop_payment_order" => "The card has been declined for an unknown reason.",
"testmode_decline" => "A Stripe test card number was used.",
"transaction_not_allowed" => "The card has been declined for an unknown reason.",
"try_again_later" => "The card has been declined for an unknown reason.",
"withdrawal_count_limit_exceeded" => "The customer has exceeded the balance or credit limit available on their card.",
];
if (array_key_exists($declineCode, $reasons)) {
return $reasons[$declineCode];
}
return $declineCode;
}
}
<?php
/**
* StripePaymentException.php
*
* This source file is subject to the GNU General Public License version 3 (GPLv3)
* For the full copyright and license information, please view the LICENSE.md
* file distributed with this source code.
*
* @copyright Copyright (c) 2012-2021 Gather Digital Ltd (https://www.gatherdigital.co.uk)
* @license https://www.gatherdigital.co.uk/license GNU General Public License version 3 (GPLv3)
* @author Paul Clegg
*/
namespace StripePaymentProvider;
class StripePaymentException extends \Exception
{
}
<?php
/**
* StripePaymentProvider.php
*
* This source file is subject to the GNU General Public License version 3 (GPLv3)
* For the full copyright and license information, please view the LICENSE.md
* file distributed with this source code.
*
* @copyright Copyright (c) 2012-2021 Gather Digital Ltd (https://www.gatherdigital.co.uk)
* @license https://www.gatherdigital.co.uk/license GNU General Public License version 3 (GPLv3)
* @author Paul Clegg
*/
namespace StripePaymentProvider;
use OnlineShop\Framework\PaymentManager\IStatus;
use \OnlineShop\Framework\PaymentManager\Payment\IPayment;
use \OnlineShop\Framework\PaymentManager\Status as PStatus;
use \OnlineShop\Framework\Model\AbstractOrder;
use \Stripe as StripeLib;
use StripePaymentProvider\Interfaces\ICustomer;
class StripePaymentProvider implements IPayment
{
const CART_TYPE_CHARGE = 'charge';
const CART_TYPE_SUBSCRIPTION = 'subscription';
protected $stripeJsSrc = 'https://js.stripe.com/v2/';
public $stripePlanClassName;
public $customerClassName;
/**
* @var \Zend_Config $apiConfigs
*/
protected $apiConfigs;
protected $defaultConfigKey = 'default';
protected $currentConfigKey;
protected $mode;
private $authorizedData;
/**
* @var $service Service
*/
private $service;
/**
* @param \Zend_Config $config
*/
public function __construct(\Zend_Config $config)
{
$this->defaultConfigKey = $config->defaultConfig;
$this->currentConfigKey = $this->defaultConfigKey;
$this->apiConfigs = $config->config;
$this->mode = $config->mode;
$this->stripePlanClassName = $config->stripePlanClass;
$this->customerClassName = $config->customerClass;
if (empty($this->apiConfigs)) {
throw new \Exception('stripe payment configuration is wrong, no config');
}
if (empty($this->apiConfigs->{$this->defaultConfigKey})) {
throw new \Exception('stripe payment configuration is wrong, invalid default config');
}
if (empty($this->apiConfigs->{$this->defaultConfigKey}->{$this->mode})) {
throw new \Exception('stripe payment configuration is wrong, invalid API mode');
}
if (!$this->getCurrentApiConfig() instanceof \Zend_Config) {
throw new \Exception('stripe payment configuration is wrong, invalid default api config');
}
if (!method_exists($this->customerClassName, 'getByEmailAndCardFingerPrint')) {
throw new \Exception('stripe payment configuration is wrong, customer class must implement \\StripePaymentProvider\\Interfaces\\ICustomer');
}
// enable Stripe Plan management
if ($this->stripePlanClassName) {
if (!method_exists($this->stripePlanClassName, 'getByStripeIdAndApiStatus')) {
throw new \Exception('stripe payment configuration is wrong, stripe class must implement \\StripePaymentProvider\\Interfaces\\IStripePlan');
}
}
$this->initCurrentConfig();
}
/**
* @return string
*/
public function getStripeJsSrc()
{
return $this->stripeJsSrc;
}
/**
* @return string
*/
public function getPublishableKey()
{
$config = $this->getCurrentApiConfig();
return $config->publishableKey;
}
/**
* @return Service
*/
public function getService()
{
if (!$this->service) {
$this->service = new Service($this);
}
return $this->service;
}
/**
* @return string
*/
public function getMode()
{
return $this->mode;
}
/**
* @return \Zend_Config|null
*/
public function getCurrentApiConfig()
{
$configKey = $this->currentConfigKey ?: $this->defaultConfigKey;
return $this->apiConfigs->{$configKey}->{$this->mode};
}
/**
* Initialises the current configuration with the Stripe API
*/
public function initCurrentConfig()
{
$this->service = null;
$this->getService();
}
/**
* @param string $key
* @return bool
*/
public function isValidConfigKey($key)
{
return (bool) $this->apiConfigs->get($key);
}
/**
* @param string $key
*/
public function setCurrentConfigKey($key)
{
$this->currentConfigKey = $key;
}
/**
* @return string
*/
public function getStripeAccountName()
{
return ucfirst($this->currentConfigKey);
}
/**
* @return bool
*/
public function isLivemode()
{
return $this->mode == 'live';
}
/**
* @return string
*/
public function getName()
{
return 'Stripe';
}
/**
* start payment
*
* @param \OnlineShop\Framework\PriceSystem\IPrice $price
* @param array $config
*
* @return StripeLib\Token
*/
public function initPayment(\OnlineShop\Framework\PriceSystem\IPrice $price, array $config)
{
//initialise different profiles for the stripe API
if ($this->isValidConfigKey($config['stripeConfigKey'])) {
$this->setCurrentConfigKey($config['stripeConfigKey']);
$this->initCurrentConfig();
}
if (strpos($config['token'], 'card_') === 0) {
// skip if is a 'source
return $config['token'];
}
// retrieve more information about the token given
$token = $this->getService()->tryApiCall('retrieve-token', $config['token']);
/**
* @var $token \Stripe\Token|\Exception
*/
// check the token for being used already
if ($token instanceof \Stripe\Token) {
if ($token->used) {
throw new \Exception('Token has been used, please try again');
}
/**
* @var $card \Stripe\Card
*/
$card = $token->card;
if (empty($card->fingerprint)) {
throw new \Exception('Invalid Card fingerprint');
}
} else {
throw $token;
}
return $token;
}
/**
* Handles response of payment provider and creates payment status object
*
* @param array $response
*
* @return \OnlineShop\Framework\PaymentManager\IStatus
*/
public function handleResponse($response)
{
// check required fields
$required = [
'stripeToken' => null,
'paymentType' => null,
'invoice' => null,
'customerId' => null
];
$authorizedData = [
'stripeToken' => null,
'paymentType' => null,
'invoice' => null,
'customerId' => null,
'stripeChargeDetail' => null
];
// check interval if paymentType is set
if ($response['paymentType'] == self::CART_TYPE_SUBSCRIPTION) {
$required['interval'] = null;
$authorizedData['interval'] = null;
}
// check fields
$response = array_intersect_key($response, $required);
if (count($required) != count($response)) {
throw new \Exception(sprintf('required fields are missing! required: %s',
implode(', ', array_keys(array_diff_key($required, $response)))));
}
// load the user by the email address
$customerClass = $this->customerClassName;
$customer = $customerClass::getById($response['customerId']);
/**
* @var $customer ICustomer
*/
if (!$customer instanceof $customerClass) {
throw new \Exception('Customer must implement the chosen class ' . $customerClass);
}
// do all of the customer creation etc here
$response['stripeChargeDetail'] = $this->getService()->translateTokenToStripeChargeDetail($response['stripeToken'], $customer);
// handle
$authorizedData = array_intersect_key($response, $authorizedData);
$this->setAuthorizedData($authorizedData);
//return an intermediate status
return new \OnlineShop\Framework\PaymentManager\Status(
$response['invoice'],
$authorizedData['email'],
null,
\OnlineShop\Framework\Model\AbstractOrder::ORDER_STATE_PAYMENT_PENDING
);
}
/**
* return the authorized data from payment provider
*
* @return array
*/
public function getAuthorizedData()
{
return $this->authorizedData;
}
/**
* set authorized data from payment provider
*
* @param array $authorizedData
*/
public function setAuthorizedData(array $authorizedData)
{
$this->authorizedData = $authorizedData;
}
/**
* execute payment
*
* @param \OnlineShop\Framework\PriceSystem\IPrice $price
* @param string $reference
*
* @return \OnlineShop\Framework\PaymentManager\IStatus
*/
public function executeDebit(\OnlineShop\Framework\PriceSystem\IPrice $price = null, $reference = null, $items = [])
{
$authData = $this->getAuthorizedData();
$stripeChargeDetail = $authData['stripeChargeDetail'];
$ret = null;
if ($authData['paymentType'] === self::CART_TYPE_CHARGE) {
// create charge
$ret = $this->getService()->tryApiCall('charge', [
"amount" => Tool::getStripeAmount($price),
"currency" => $price->getCurrency()->getShortName(),
"customer" => $stripeChargeDetail['stripeCustomerId'],
"source" => $stripeChargeDetail['stripePaymentSource'],
"description" => "Card Charge",
"capture" => true,
"receipt_email" => null,
"metadata" => [
"reference" => $reference,
]
]);
} else if ($authData['paymentType'] === self::CART_TYPE_SUBSCRIPTION) {
// get the plan that matches this customers options
$authData['planId'] = $this->getService()->translatePaymentAuthDataToStripePlan($authData, $price, $items);
// subscribe the current customer to the plan
$ret = $this->getService()->tryApiCall('create-subscription', [
"customer" => $stripeChargeDetail['stripeCustomerId'],
"source" => $stripeChargeDetail['stripePaymentSource'],
'plan' => $authData['planId']
]);
}
// throw the exception if one is returned
if ($ret instanceof \Exception) {
throw $ret;
}
if ($ret instanceof \Stripe\Charge && $ret->paid === true) {
return new PStatus($reference, $ret->id, null, AbstractOrder::ORDER_STATE_COMMITTED, [
'stripe_TransactionType' => $ret->object,
'stripe_amount' => $ret->amount,
'stripe_id' => $ret->id
]);
} else if ($ret instanceof \Stripe\Subscription) {
return new PStatus($reference, $ret->id, null, AbstractOrder::ORDER_STATE_COMMITTED, [
'stripe_TransactionType' => $ret->object,
'stripe_amount' => $ret->plan->amount,
'stripe_id' => $ret->id
]);
}
// failed ($ret is a string)
return new PStatus($reference, $authData['token'], $ret, AbstractOrder::ORDER_STATE_ABORTED);
}
/**
* execute credit
*
* @param \OnlineShop\Framework\PriceSystem\IPrice $price
* @param string $reference
* @param $transactionId
* @return IStatus
* @throws \Exception
*/
public function executeCredit(\OnlineShop\Framework\PriceSystem\IPrice $price, $reference, $transactionId)
{
throw new \Exception('Not implemented');
}
}
<?php
/**
* Tool.php
*
* This source file is subject to the GNU General Public License version 3 (GPLv3)
* For the full copyright and license information, please view the LICENSE.md
* file distributed with this source code.
*
* @copyright Copyright (c) 2012-2021 Gather Digital Ltd (https://www.gatherdigital.co.uk)
* @license https://www.gatherdigital.co.uk/license GNU General Public License version 3 (GPLv3)
* @author Paul Clegg
*/
namespace StripePaymentProvider;
use OnlineShop\Framework\PriceSystem\IPrice;
class Tool
{
/**
* @param \OnlineShop\Framework\PriceSystem\IPrice $price
* @return int|float
*/
public static function getStripeAmount(IPrice $price)
{
$amount = $price->getAmount();
$zeroDecimalCurrencies = [
'BIF',
'CLP',
'DJF',
'GNF',
'JPY',
'KMF',
'KRW',
'MGA',
'PYG',
'RWF',
'VND',
'VUV',
'XAF',
'XOF',
'XPF'
];
return (int) in_array($price->getCurrency(), $zeroDecimalCurrencies) ? $amount : $amount * 100;
}
public static function getIdempotencyKey()
{
$uuid5 = \Ramsey\Uuid\Uuid::uuid4();
return $uuid5->toString();
}
/**
* Returns a valid Stripe Plan Name Given plan information
* - Will be returned in context to the current API settings
* @param \OnlineShop\Framework\PriceSystem\IPrice $price
* @param $interval
* @param int $intervalCount
* @param StripePaymentProvider $payment
* @return string
*/
public static function getValidStripePlanName(IPrice $price, $interval, $intervalCount=1, StripePaymentProvider $payment)
{
// e.g. "1000USD 1M EU Test"
return implode(' ', [
$price->getAmount().strtoupper($price->getCurrency()->getShortName()),
$intervalCount,
strtoupper($interval{0}), // should be D|W|M|Y
strtoupper($payment->getStripeAccountName()),
ucfirst($payment->getMode())
]);
}
/**
* Returns a valid Stripe Plan ID Given plan information
* - Will be returned in context to the current API settings
* - Is also compatible with Pimcore Object "Keys", so can be used in setKey()
* @param \OnlineShop\Framework\PriceSystem\IPrice $price
* @param string $interval
* @param int $intervalCount
* @param StripePaymentProvider $payment
* @return string
*/
public static function getValidStripePlanId(IPrice $price, $interval, $intervalCount=1, $payment)
{
$planName = self::getValidStripePlanName($price, $interval, $intervalCount, $payment);
return self::getValidStripePlanKey($planName);
}
/**
* Taken from Pimcore 4.3.1 File::getValidFilename (Pimcore now allows caps & whitespace)
* @param $planName
* @return mixed|string
*/
public static function getValidStripePlanKey($planName)
{
$tmpFilename = \Pimcore\Tool\Transliteration::toASCII($planName);
$tmpFilename = strtolower($tmpFilename);
$tmpFilename = preg_replace('/[^a-z0-9\-\.~_]+/', '-', $tmpFilename);
$tmpFilename = ltrim($tmpFilename, "."); // files shouldn't start with a "." (=hidden file)
return $tmpFilename;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment