<?php
/**
 * @table paysystems
 * @id stripe
 * @title Stripe ApplePay
 * @visible_link https://stripe.com/
 * @logo_url stripe.png
 * @recurring amember
 * @am_payment_api 6.0
 */
class Am_Paysystem_StripeApplePay extends Am_Paysystem_CreditCard
{
    const PLUGIN_STATUS = self::STATUS_DEV;
    const PLUGIN_DATE = '$Date$';
    const PLUGIN_REVISION = '6.3.43';

    const TOKEN = 'stripe_apple_pay_token';

    const API_VERSION = '2020-08-27';

    protected $_pciDssNotRequired = true;

    protected $defaultTitle = "Stripe";
    protected $defaultDescription  = "Apple Pay Payments";

    public function allowPartialRefunds()
    {
        return true;
    }

    public function getSupportedCurrencies()
    {
        return array_keys(Am_Currency::getFullList());
    }

    protected function _initSetupForm(Am_Form_Setup $form)
    {
        $form->addText('public_key', ['class'=>'am-el-wide'])
            ->setLabel('Publishable Key')
            ->addRule('required')
            ->addRule('regex', 'Publishable Key must start with "pk_"', '/^pk_.*$/');

        $form->addSecretText('secret_key', ['class'=>'am-el-wide'])
            ->setLabel('Secret Key')
            ->addRule('required')
            ->addRule('regex', 'Secret Key must start with "sk_"', '/^sk_.*$/');
        $countries = ['US', 'AT', 'AU', 'BE', 'BR', 'CA', 'CH', 'DE', 'DK', 'EE', 'ES', 'FI', 'FR', 'GB', 'HK', 'IE', 'IN', 'IT', 'JP', 'LT', 'LU', 'LV', 'MX', 'NL', 'NZ', 'NO', 'PH', 'PL', 'PT', 'RO', 'SE', 'SG', 'SK'];
        $form->addSelect('country')->setLabel('The two-letter country code of your Stripe account')
            ->loadOptions(array_combine($countries, $countries));
    }

    public function isNotAcceptableForInvoice(Invoice $invoice)
    {
        if ($ret = parent::isNotAcceptableForInvoice($invoice))
            return $ret;
        foreach ($invoice->getItems() as $item) {
            if (!(float)$item->first_total && (float)$item->second_total) {
                return ["Stripe Apple Pay does not support products with free trial"];
            }
            if ($item->rebill_times &&
                $item->second_period != $item->first_period &&
                $item->second_period != Am_Period::MAX_SQL_DATE){

                return [___("Stripe Apple Pay is unable to handle billing for product [{$item->item_title}] - second_period must be equal to first_period")];
            }
        }
    }

    public function _doBill(Invoice $invoice, $doFirst, CcRecord $cc, Am_Paysystem_Result $result)
    {
        if (!$token = $invoice->getUser()->data()->get(self::TOKEN)) {
            return $result->setErrorMessages([___('Payment failed')]);
        }
        if ($doFirst && (doubleval($invoice->first_total) <= 0))
        { // free trial
            $tr = new Am_Paysystem_Transaction_Free($this);
            $tr->setInvoice($invoice);
            $tr->process();
            $result->setSuccess($tr);
        } else {
            $tr = new Am_Paysystem_Transaction_StripeApplePay_Charge($this, $invoice, $doFirst);
            $tr->run($result);
        }
    }

    public function getUpdateCcLink($user)
    {
        //
    }

    public function storeCreditCard(CcRecord $cc, Am_Paysystem_Result $result)
    {
        //
    }

    public function loadCreditCard(Invoice $invoice)
    {
        if ($invoice->getUser()->data()->get(self::TOKEN))
            return $this->getDi()->CcRecordTable->createRecord(); // return fake record for rebill
    }

    protected function createController(Am_Mvc_Request $request, Am_Mvc_Response $response, array $invokeArgs)
    {
        return new Am_Mvc_Controller_StripeApplePay($request, $response, $invokeArgs);
    }

    public function processRefund(InvoicePayment $payment, Am_Paysystem_Result $result, $amount)
    {
        $tr = new Am_Paysystem_Transaction_StripeApplePay_Refund($this, $payment->getInvoice(), $payment->receipt_id, $amount);
        $tr->run($result);
    }


}

class Am_Mvc_Controller_StripeApplePay extends Am_Mvc_Controller
{
    /** @var Invoice*/
    protected $invoice;
    /** @var Am_Paysystem_Stripe */
    protected $plugin;

    public function setInvoice(Invoice $invoice) { $this->invoice = $invoice; }
    public function setPlugin($plugin) { $this->plugin = $plugin; }

    public function createForm($label, $cc_mask = null)
    {
        $form = new Am_Form('cc-stripe');

        $key = $this->plugin->getConfig('public_key');
        $country = $this->plugin->getConfig('country');
        $amount = $this->invoice->first_total * (pow(10, Am_Currency::$currencyList[$this->invoice->currency]['precision']));
        $currency = strtolower($this->invoice->currency);
        $description = $this->invoice->getLineDescription();

        $cancel = $this->getDi()->surl("cancel", ['id'=>$this->invoice->getSecureId("CANCEL")], false);

        $thanks = $this->getDi()->surl("thanks", ['id'=>$this->invoice->getSecureId("THANKS")], false);

        $id = $this->getParam('id');
        $form->addHidden('id')->setValue($id);
        $form->addHidden('stripe_info', 'id=stripe_info');
        $form->addStatic(null, ['class' => 'am-row-wide'])->setContent(<<<CUT
<div id="payment-request-button">
  <!-- A Stripe Element will be inserted here. -->
</div>
<script type="text/javascript">
var stripe = Stripe('$key');
var paymentRequest = stripe.paymentRequest({
  country: '$country',
  currency: '$currency',
  total: {
    label: '$description',
    amount: $amount,
  },
  requestPayerName: true,
  requestPayerEmail: true,
});
var elements = stripe.elements();
var prButton = elements.create('paymentRequestButton', {
  paymentRequest: paymentRequest,
});

// Check the availability of the Payment Request API first.
paymentRequest.canMakePayment().then(function(result) {
  if (result) {
    prButton.mount('#payment-request-button');
  } else {
    document.getElementById('payment-request-button').style.display = 'none';
    window.location.replace('$cancel');
  }
});
paymentRequest.on('token', function(ev) {
  var frm = jQuery("form#cc-stripe");
  jQuery("#stripe_info").val(JSON.stringify(ev.token));
  frm.submit();
  return;
});
</script>
CUT
        );

        return $form;
    }

    protected function ccFormAndSaveCustomer()
    {
        $vars = $this->form->getValue();
        $result = new Am_Paysystem_Result();
        if (!empty($vars['stripe_info']))
        {
            $stripe_info = json_decode($vars['stripe_info'], true);

            if (!$stripe_info['id']) {
                throw new Am_Exception_Paysystem("No expected token id received");
            }

            $result = $this->processToken($stripe_info['id']);
        }
        return $result;
    }

    function processToken($token)
    {
        $result = new Am_Paysystem_Result();

        $tr = new Am_Paysystem_Transaction_StripeApplePay_CreateCustomer($this->plugin, $this->invoice, $token);
        $tr->run($result);

        if ($result->isSuccess())
        {
            $this->invoice->getUser()->data()
                ->set(Am_Paysystem_StripeApplePay::TOKEN, $cus = $tr->getUniqId())
                ->update();
        }
        return $result;
    }

    public function ccAction()
    {
        $this->view->title = ___('Payment info');
        $this->view->display_receipt = true;
        $this->view->layoutNoMenu = true;
        $this->view->invoice = $this->invoice;

        $this->form = $this->createForm(___('Subscribe And Pay'));

        $result = $this->ccFormAndSaveCustomer(floatval($this->invoice->first_total) > 0); //do not use 3DS for free trial
        if ($result->isSuccess())
        {
            $result = $this->plugin->doBill($this->invoice, $this->invoice->isFirstPayment(), $this->getDi()->CcRecordTable->createRecord());
            if ($result->isSuccess())
            {
                return $this->redirect($this->plugin->getReturnUrl());
            } else {
                $this->invoice->getUser()->data()
                    ->set(Am_Paysystem_StripeApplePay::TOKEN, null)
                    ->update();
                $this->view->error = $result->getErrorMessages();
            }
        }
        $this->view->headScript()->appendFile('https://js.stripe.com/v3/');
        $this->view->form = $this->form;
        $this->view->display('cc/info.phtml');
    }
}

class Am_Paysystem_Transaction_StripeApplePay_CreateCustomer extends Am_Paysystem_Transaction_CreditCard
{
    protected $parsedResponse = [];

    public function __construct(Am_Paysystem_Abstract $plugin, Invoice $invoice, $token)
    {
        //if(strpos($token, 'tok_') !== 0) print_rre($token);
        $request = new Am_HttpRequest('https://api.stripe.com/v1/customers', 'POST');
        $request->setAuth($plugin->getConfig('secret_key'), '')
            ->addPostParameter('source', $token)
            ->addPostParameter('email', $invoice->getEmail())
            ->addPostParameter('description', 'Username:' . $invoice->getUser()->login);
        parent::__construct($plugin, $invoice, $request, true);
    }

    public function getUniqId()
    {
        return $this->parsedResponse['id'];
    }

    public function parseResponse()
    {
        $this->parsedResponse = json_decode($this->response->getBody(), true);
    }

    public function validate()
    {
        if (empty($this->parsedResponse['id']))
        {
            $code = @$this->parsedResponse['error']['code'];
            $message = @$this->parsedResponse['error']['message'];
            $error = "Error storing customer profile";
            if ($code) { $error .= " [{$code}]"; }
            if ($message) { $error .= " ({$message})"; }
            $this->result->setFailed($error);
            return false;
        }
        $this->result->setSuccess($this);
        return true;
    }

    public function processValidated()
    {
        //nop
    }
}

class Am_Paysystem_Transaction_StripeApplePay_Charge extends Am_Paysystem_Transaction_CreditCard
{
    protected $parsedResponse = [];

    public function __construct(Am_Paysystem_Abstract $plugin, Invoice $invoice, $doFirst)
    {
        $request = new Am_HttpRequest('https://api.stripe.com/v1/payment_intents', 'POST');
        $request->setHeader('Stripe-Version', Am_Paysystem_StripeApplePay::API_VERSION);
        $amount = $doFirst ? $invoice->first_total : $invoice->second_total;
        $request->setAuth($plugin->getConfig('secret_key'), '')
            ->addPostParameter('amount', $amount * (pow(10, Am_Currency::$currencyList[$invoice->currency]['precision'])))
            ->addPostParameter('currency', $invoice->currency)
            ->addPostParameter('customer', $invoice->getUser()->data()->get(Am_Paysystem_StripeApplePay::TOKEN))
            ->addPostParameter('confirm', 'true')
            ->addPostParameter('metadata[invoice]', $invoice->public_id)
            ->addPostParameter('description', 'Invoice #'.$invoice->public_id.': '.$invoice->getLineDescription());
        parent::__construct($plugin, $invoice, $request, true);
    }

    public function getUniqId()
    {
        return $this->parsedResponse['charges']['data'][0]['id']?:$this->parsedResponse['id'];
    }

    public function parseResponse()
    {
        $this->parsedResponse = json_decode($this->response->getBody(), true);
    }

    function validate()
    {
        if (!empty($this->parsedResponse['error'])) {
            $this->result->setFailed($this->parsedResponse['error']['message']);
        }
        else {
            if ($this->parsedResponse['status'] != 'succeeded') {
                $this->result->setFailed(___('Status is not succeeded'));
            }
            else {
                $this->result->setSuccess($this);
            }
        }
    }
}

class Am_Paysystem_Transaction_StripeApplePay_Refund extends Am_Paysystem_Transaction_CreditCard
{
    protected $parsedResponse = [];
    protected $charge_id;
    protected $amount;

    public function __construct(Am_Paysystem_Abstract $plugin, Invoice $invoice, $charge_id, $amount = null)
    {
        $this->charge_id = $charge_id;
        $this->amount = $amount > 0 ? $amount : null;
        $request = new Am_HttpRequest('https://api.stripe.com/v1/charges/' . $this->charge_id . '/refund', 'POST');
        $request->setAuth($plugin->getConfig('secret_key'), '');
        if ($this->amount > 0) {
            $request->addPostParameter('amount', $this->amount * (pow(10, Am_Currency::$currencyList[$invoice->currency]['precision'])));
        }
        parent::__construct($plugin, $invoice, $request, true);
    }

    public function getUniqId()
    {
        $r = null;
        foreach ($this->parsedResponse['refunds']['data'] as $refund) {
            if (is_null($r) || $refund['created'] > $r['created']) {
                $r = $refund;
            }
        }
        return $r['id'];
    }

    public function parseResponse()
    {
        $this->parsedResponse = json_decode($this->response->getBody(), true);
    }

    public function validate()
    {
        if (empty($this->parsedResponse['id'])) {
            $this->result->setFailed('Unable to fetch payment profile');
        } else {
            $this->result->setSuccess();
        }
    }

    public function processValidated()
    {
        $this->invoice->addRefund($this, $this->charge_id, $this->amount);
    }
}
