\n"
+"X-Generator: node-wp-i18n 1.2.3\n"
+
+#: includes/admin/CouponsMetaBox.php:22
+msgid "Número de ciclos do cupom"
+msgstr ""
+
+#: includes/admin/CouponsMetaBox.php:72
+msgid "Sign Up Fee Discount"
+msgstr ""
+
+#: includes/admin/CouponsMetaBox.php:73
+msgid "Sign Up Fee % Discount"
+msgstr ""
+
+#: includes/admin/CouponsMetaBox.php:74
+msgid "Recurring Product Discount"
+msgstr ""
+
+#: includes/admin/CouponsMetaBox.php:75
+msgid "Recurring Product % Discount"
+msgstr ""
+
+#: includes/admin/Settings.php:91 templates/admin-gateway-settings.html.php:12
+#: templates/admin-settings.html.php:12 views/product-status.php:15
+#: views/product-status.php:19 views/product-status.php:23
+msgid "Vindi"
+msgstr ""
+
+#: includes/admin/Settings.php:130
+msgid "Ver Logs"
+msgstr ""
+
+#: includes/admin/Settings.php:131
+msgid "Saiba mais"
+msgstr ""
+
+#: includes/admin/Settings.php:133
+msgid "Não possui uma conta?"
+msgstr ""
+
+#: includes/admin/Settings.php:135
+msgid "Dúvidas?"
+msgstr ""
+
+#: includes/admin/Settings.php:139
+msgid "Chave da API Vindi"
+msgstr ""
+
+#: includes/admin/Settings.php:145
+msgid "Emissão de NFe's"
+msgstr ""
+
+#: includes/admin/Settings.php:146
+msgid "Enviar informações para emissão de NFe's"
+msgstr ""
+
+#: includes/admin/Settings.php:148
+msgid ""
+"Envia informações de RG e Inscrição Estadual para Emissão de NFe's com "
+"nossos parceiros. %s"
+msgstr ""
+
+#: includes/admin/Settings.php:152
+msgid "Status de conclusão do pedido"
+msgstr ""
-#. URI of the plugin
-msgid "#!"
+#: includes/admin/Settings.php:154
+msgid ""
+"Status que o pedido deverá ter após receber a confirmação de pagamento da "
+"Vindi."
+msgstr ""
+
+#: includes/admin/Settings.php:163
+msgid "Sincronismo de Status das Assinaturas"
+msgstr ""
+
+#: includes/admin/Settings.php:165
+msgid "Enviar alterações de status nas assinaturas do WooCommerce"
+msgstr ""
+
+#: includes/admin/Settings.php:166
+msgid "Envia as alterações de status nas assinaturas do WooCommerce para Vindi."
+msgstr ""
+
+#: includes/admin/Settings.php:170
+msgid "Cobrança única"
msgstr ""
-#. Description of the plugin
-msgid "Adiciona o gateway de pagamentos da Vindi para o WooCommerce."
+#: includes/admin/Settings.php:172
+msgid "Ativar cobrança única para fretes e taxas"
msgstr ""
-#. Author URI of the plugin
-msgid "https://www.vindi.com.br"
+#: includes/admin/Settings.php:173
+msgid "Fretes e Taxas serão cobrados somente no primeiro ciclo de uma assinatura"
msgstr ""
-#: src/includes/Settings.php:126
+#: includes/admin/Settings.php:177
msgid "Testes"
msgstr ""
-#. Author of the plugin
-msgid "vindi"
+#: includes/admin/Settings.php:181
+msgid "Ambiente Sandbox"
+msgstr ""
+
+#: includes/admin/Settings.php:182
+msgid "Ativar Sandbox"
+msgstr ""
+
+#: includes/admin/Settings.php:184
+msgid ""
+"Ative esta opção para habilitar a comunicação com o ambiente Sandbox da "
+"Vindi."
+msgstr ""
+
+#: includes/admin/Settings.php:188
+msgid "Log de Depuração"
+msgstr ""
+
+#: includes/admin/Settings.php:189
+msgid "Ativar Logs"
+msgstr ""
+
+#: includes/admin/Settings.php:191
+msgid "Ative esta opção para habilitar logs de depuração do servidor. %s"
+msgstr ""
+
+#: includes/gateways/BankSlipPayment.php:35
+msgid "Vindi - Boleto Bancário"
+msgstr ""
+
+#: includes/gateways/BankSlipPayment.php:36
+msgid "Aceitar pagamentos via boleto bancário utilizando a Vindi."
+msgstr ""
+
+#: includes/gateways/BankSlipPayment.php:83
+#: includes/gateways/CreditPayment.php:90
+msgid "Habilitar/Desabilitar"
+msgstr ""
+
+#: includes/gateways/BankSlipPayment.php:84
+msgid "Habilitar pagamento por Boleto Bancário com Vindi"
+msgstr ""
+
+#: includes/gateways/BankSlipPayment.php:89
+#: includes/gateways/CreditPayment.php:96
+msgid "Título"
+msgstr ""
+
+#: includes/gateways/BankSlipPayment.php:91
+#: includes/gateways/CreditPayment.php:98
+msgid "Título que o cliente verá durante o processo de pagamento."
+msgstr ""
+
+#: includes/gateways/BankSlipPayment.php:92
+msgid "Boleto Bancário"
+msgstr ""
+
+#: includes/gateways/BankSlipPayment.php:102
+msgid "Selecione o País para visualizar as formas de pagamento."
+msgstr ""
+
+#: includes/gateways/BankSlipPayment.php:107
+msgid "Este método de pagamento não é aceito."
+msgstr ""
+
+#: includes/gateways/CreditPayment.php:44
+msgid "Vindi - Cartão de Crédito"
+msgstr ""
+
+#: includes/gateways/CreditPayment.php:45
+msgid "Aceitar pagamentos via cartão de crédito utilizando a Vindi."
+msgstr ""
+
+#: includes/gateways/CreditPayment.php:91
+msgid "Habilitar pagamento via Cartão de Crédito com a Vindi"
+msgstr ""
+
+#: includes/gateways/CreditPayment.php:99
+msgid "Cartão de Crédito"
+msgstr ""
+
+#: includes/gateways/CreditPayment.php:102
+msgid "Transação de Verificação"
+msgstr ""
+
+#: includes/gateways/CreditPayment.php:104
+msgid ""
+" Realiza a transação de verificação em todos os novos pedidos. (Taxas "
+"adicionais por verificação poderão ser cobradas)."
+msgstr ""
+
+#: includes/gateways/CreditPayment.php:108
+msgid "Vendas Avulsas"
+msgstr ""
+
+#: includes/gateways/CreditPayment.php:112
+msgid "Valor mínimo da parcela"
+msgstr ""
+
+#: includes/gateways/CreditPayment.php:114
+msgid "Valor mínimo da parcela, não deve ser inferior a R$ 5,00."
+msgstr ""
+
+#: includes/gateways/CreditPayment.php:118
+msgid "Número máximo de parcelas"
+msgstr ""
+
+#: includes/gateways/CreditPayment.php:120
+msgid ""
+"Número máximo de parcelas para vendas avulsas. Deixe em 1x para desativar o "
+"parcelamento."
+msgstr ""
+
+#: includes/gateways/CreditPayment.php:138
+msgid "Habilitar juros"
+msgstr ""
+
+#: includes/gateways/CreditPayment.php:140
+msgid "Habilitar juros no parcelamento do pedido."
msgstr ""
-#: src/views/ecfb-missing.php:20 src/views/woocommerce-missing.php:20
-msgid "Vindi Disabled"
+#: includes/gateways/CreditPayment.php:144
+msgid "Taxa de juros ao mês (%)"
msgstr ""
-#. Name of the plugin
-msgid "Vindi WooCommerce"
+#: includes/gateways/CreditPayment.php:146
+msgid "Taxa de juros que será adicionada aos pagamentos parcelados."
msgstr ""
-#: src/views/woocommerce-missing.php:20
-msgid "WooCommerce"
+#: includes/gateways/CreditPayment.php:160 utils/InterestPriceHandler.php:62
+#: utils/PaymentProcessor.php:463
+msgid "Juros"
+msgstr ""
+
+#: includes/gateways/CreditPayment.php:182
+msgid ""
+"Estamos enfrentando problemas técnicos no momento. Tente novamente mais "
+"tarde ou entre em contato."
+msgstr ""
+
+#: services/Webhooks.php:38
+msgid "Novo Webhook chamado: %s"
+msgstr ""
+
+#: services/Webhooks.php:67
+msgid "Falha ao interpretar JSON do webhook: Evento do Webhook não encontrado!"
+msgstr ""
+
+#: services/Webhooks.php:73
+msgid "Novo Evento processado: %s"
+msgstr ""
+
+#: services/Webhooks.php:77
+msgid "Evento do webhook ignorado pelo plugin: "
+msgstr ""
+
+#: services/Webhooks.php:86
+msgid "Evento de teste do webhook."
+msgstr ""
+
+#: services/Webhooks.php:99
+msgid "Já existe o ciclo %s para a assinatura #%s pedido #%s!"
+msgstr ""
+
+#: services/Webhooks.php:195 utils/PaymentProcessor.php:931
+msgid "O Pagamento foi realizado com sucesso pela Vindi."
+msgstr ""
+
+#: services/Webhooks.php:215
+msgid "Pagamento cancelado dentro da Vindi!"
+msgstr ""
+
+#: services/Webhooks.php:229
+msgid "Pendência criada com o tipo \"%s\" não processada!"
+msgstr ""
+
+#: services/Webhooks.php:232
+msgid "Pendência criada com o status \"%s\" não processada!"
+msgstr ""
+
+#: services/Webhooks.php:235
+msgid "Pendência criada com o item do tipo \"%s\" não processada!"
+msgstr ""
+
+#: services/Webhooks.php:243
+msgid ""
+"Divergencia de valores do Pedido #%s: Valor Esperado R$ %s, Valor Pago R$ "
+"%s."
+msgstr ""
+
+#: services/Webhooks.php:259
+msgid "Pagamento rejeitado!"
+msgstr ""
+
+#: services/Webhooks.php:262
+msgid ""
+"Erro ao trocar status da fatura para \"failed\" pois a fatura #%s não está "
+"mais pendente!"
msgstr ""
-#: src/views/ecfb-missing.php:20
-msgid "WooCommerce Extra Checkout Fields for Brazil"
+#: services/Webhooks.php:299
+msgid "Assinatura %s reativada pela Vindi."
msgstr ""
-#: src/views/ecfb-missing.php:20 src/views/woocommerce-missing.php:20
-#, php-format
-msgid "WooCommerce vindi requires the latest version of %s to work!"
+#: services/Webhooks.php:313
+msgid "Assinatura #%s não encontrada!"
msgstr ""
+
+#: services/Webhooks.php:328
+msgid "Cobrança #%s não encontrada!"
+msgstr ""
+
+#: services/Webhooks.php:344
+msgid "Pedido #%s não encontrado!"
+msgstr ""
+
+#: services/Webhooks.php:368
+msgid "Pedido com bill_id #%s não encontrado!"
+msgstr ""
+
+#: services/Webhooks.php:392
+msgid "Pedido da assinatura #%s para o ciclo #%s não encontrado!"
+msgstr ""
+
+#: templates/admin-gateway-settings.html.php:6
+#: templates/admin-settings.html.php:6
+msgid "Vindi WooCommerce Desabilitado"
+msgstr ""
+
+#: templates/admin-gateway-settings.html.php:7
+msgid ""
+"É necessário um Certificado SSL para ativar este método "
+"de pagamento em modo de produção. Por favor, verifique se um certificado "
+"SSL está instalado em seu servidor !"
+msgstr ""
+
+#: templates/admin-gateway-settings.html.php:13
+#: templates/admin-settings.html.php:13
+msgid "Utiliza a rede Vindi como meio de pagamento para cobranças."
+msgstr ""
+
+#: templates/admin-settings.html.php:7
+msgid ""
+"É necessário um Certificado SSL para ativar este método de "
+"pagamento em modo de produção. Por favor, verifique se um certificado SSL "
+"está instalado em seu servidor!"
+msgstr ""
+
+#: templates/admin-settings.html.php:26
+msgid "Link de configuração dos Eventos da Vindi"
+msgstr ""
+
+#: templates/admin-settings.html.php:29
+msgid ""
+"Copie esse link e utilize-o para configurar os eventos nos Webhooks da "
+"Vindi."
+msgstr ""
+
+#: templates/admin-settings.html.php:38 templates/admin-settings.html.php:49
+msgid "Teste de conexão com a Vindi"
+msgstr ""
+
+#: templates/admin-settings.html.php:39
+msgid "Conectado com sucesso!"
+msgstr ""
+
+#: templates/admin-settings.html.php:42
+msgid "Conta: %s "
+msgstr ""
+
+#: templates/admin-settings.html.php:45
+msgid "Status: %s "
+msgstr ""
+
+#: templates/admin-settings.html.php:50
+msgid "Falha na conexão! %s "
+msgstr ""
+
+#: templates/bankslip-checkout.html.php:5
+#: templates/creditcard-checkout.html.php:5
+msgid "MODO DE TESTES"
+msgstr ""
+
+#: templates/bankslip-checkout.html.php:7
+#: templates/creditcard-checkout.html.php:7
+msgid ""
+"Sua conta na Vindi está em Modo Trial . Este modo é "
+"proposto para a realização de testes e, portanto, nenhum pedido será "
+"efetivamente cobrado."
+msgstr ""
+
+#: templates/bankslip-checkout.html.php:18
+msgid "Um Boleto Bancário será enviado para o seu endereço de e-mail."
+msgstr ""
+
+#: templates/bankslip-checkout.html.php:20
+msgid ""
+"Um boleto bancário será enviado para o seu e-mail de acordo com a sua "
+"assinatura. "
+msgstr ""
+
+#: templates/bankslip-download.html.php:8
+msgid "Aqui estão os boletos de suas compras."
+msgstr ""
+
+#: templates/bankslip-download.html.php:11
+msgid ""
+"Você pode imprimi-los e pagá-los via internet banking ou em agências "
+"bancárias e loréricas. Após recebermos a confirmação do pagamento, seu "
+"pedido será processado."
+msgstr ""
+
+#: templates/bankslip-download.html.php:23
+msgid "Baixar boleto"
+msgstr ""
+
+#: templates/creditcard-checkout.html.php:20
+msgid "Cartão Cadastrado"
+msgstr ""
+
+#: templates/creditcard-checkout.html.php:31
+msgid "usar outro cartão"
+msgstr ""
+
+#: templates/creditcard-checkout.html.php:60
+msgid "Nome"
+msgstr ""
+
+#: templates/creditcard-checkout.html.php:61
+msgid "Validade"
+msgstr ""
+
+#: templates/creditcard-checkout.html.php:62
+#: templates/creditcard-checkout.html.php:143
+msgid "Número do Cartão"
+msgstr ""
+
+#: templates/creditcard-checkout.html.php:123
+msgid "CVC"
+msgstr ""
+
+#: templates/creditcard-checkout.html.php:136
+msgid "Nome Impresso no Cartão"
+msgstr ""
+
+#: templates/creditcard-checkout.html.php:153
+msgid "Validade (mm/aa)"
+msgstr ""
+
+#: templates/creditcard-checkout.html.php:160
+msgid "Código de Segurança"
+msgstr ""
+
+#: templates/creditcard-checkout.html.php:173
+msgid "Número de Parcelas"
+msgstr ""
+
+#: templates/creditcard-checkout.html.php:178
+msgid "%dx de %s"
+msgstr ""
+
+#: utils/PaymentGateway.php:188
+msgid ""
+"O Pagamento foi cancelado devido a erro de configuração do meio de "
+"pagamento."
+msgstr ""
+
+#: utils/PaymentGateway.php:231
+msgid "Reembolso falhou."
+msgstr ""
+
+#: utils/PaymentGateway.php:235
+msgid ""
+"Não é possível realizar reembolsos parciais, faça um reembolso manual caso "
+"você opte por esta opção."
+msgstr ""
+
+#: utils/PaymentGateway.php:250
+#. translators: 1: Refund amount, 2: Refund ID
+msgid "[Transação #%2$s]: reembolsado R$%1$s"
+msgstr ""
+
+#: utils/PaymentGateway.php:296
+msgid "Resposta vazia"
+msgstr ""
+
+#: utils/PaymentGateway.php:307
+msgid "A fatura com bill_id #%s não foi encontrada!"
+msgstr ""
+
+#: utils/PaymentProcessor.php:204
+msgid ""
+"Falha ao processar carrinho de compras. Verifique os itens escolhidos e "
+"tente novamente."
+msgstr ""
+
+#: utils/PaymentProcessor.php:218
+msgid ""
+"Não é possível comprar produtos simples e assinaturas com trial no mesmo "
+"pedido!"
+msgstr ""
+
+#: utils/PaymentProcessor.php:255 utils/PaymentProcessor.php:275
+msgid "Algum pagamento do pedido não pode ser processado"
+msgstr ""
+
+#: utils/PaymentProcessor.php:309
+msgid ""
+"Falha ao registrar o método de pagamento. Verifique os dados e tente "
+"novamente."
+msgstr ""
+
+#: utils/PaymentProcessor.php:325
+msgid "Não foi possível realizar a verificação do seu cartão de crédito!"
+msgstr ""
+
+#: utils/PaymentProcessor.php:376
+msgid "Ocorreu um erro ao gerar o seu pedido!"
+msgstr ""
+
+#: utils/PaymentProcessor.php:403
+msgid ""
+"Falha ao recuperar informações sobre o produto na Vindi. Verifique os dados "
+"e tente novamente."
+msgstr ""
+
+#: utils/PaymentProcessor.php:777
+msgid "O produto selecionado não é uma assinatura."
+msgstr ""
+
+#: utils/PaymentProcessor.php:809 utils/PaymentProcessor.php:843
+msgid "Pagamento Falhou. (%s)"
+msgstr ""
+
+#: utils/PaymentProcessor.php:934
+msgid "Aguardando pagamento do pedido."
+msgstr ""
+
+#: utils/RedirectCheckout.php:29
+msgid "Para finalizar sua compra, é necessário estar logado"
+msgstr ""
+
+#: utils/RedirectCheckout.php:31
+msgid "Clique aqui para acessar uma conta existente ou criá-la."
+msgstr ""
+
+#: utils/SubscriptionStatusHandler.php:123
+#. translators: $1: opening link tag, $2: order number, $3: closing link tag
+msgid "A assinatura foi cancelada pelo pedido reembolsado %1$s#%2$s%3$s."
+msgstr ""
+
+#: views/invalid-api-key.php:13
+msgid "Chave API Inválida"
+msgstr ""
+
+#: views/invalid-api-key.php:13
+msgid ""
+"A chave API utilizada não é uma chave válida! Por favor verifique as "
+"informações e tente novamente!"
+msgstr ""
+
+#: views/missing-critical-dependency.php:15
+msgid ""
+"O Plugin Vindi WooCommerce depende da versão %s+ do %s para funcionar! Como "
+"a versão atual do %s é mais antiga, o plugin foi DESATIVADO!"
+msgstr ""
+
+#: views/missing-dependency.php:15
+msgid "O Plugin Vindi WooCommerce depende da versão %s do %s para funcionar!"
+msgstr ""
+
+#: views/product-status.php:15
+msgid "O produto foi criado na Vindi com sucesso!"
+msgstr ""
+
+#: views/product-status.php:19
+msgid "O produto foi atualizado na Vindi com sucesso!"
+msgstr ""
+
+#: views/product-status.php:23
+msgid "Não foi possível criar/atualizar o produto na Vindi!"
+msgstr ""
\ No newline at end of file
diff --git a/src/includes/admin/CouponsMetaBox.php b/src/includes/admin/CouponsMetaBox.php
new file mode 100644
index 0000000..34c8ce9
--- /dev/null
+++ b/src/includes/admin/CouponsMetaBox.php
@@ -0,0 +1,79 @@
+ 'cycle_count',
+ 'label' => __( 'Número de ciclos do cupom', VINDI ),
+ 'value' => get_post_meta($coupon->get_id(), 'cycle_count')[0],
+ 'options' => array(
+ '0' => 'Todos os ciclos',
+ '1' => '1 ciclo',
+ '2' => '2 ciclos',
+ '3' => '3 ciclos',
+ '4' => '4 ciclos',
+ '5' => '5 ciclos',
+ '6' => '6 ciclos',
+ '7' => '7 ciclos',
+ '8' => '8 ciclos',
+ '9' => '9 ciclos',
+ '10' => '10 ciclos',
+ '11' => '11 ciclos',
+ '12' => '12 ciclos',
+ ),
+ )
+ );
+ }
+
+ /**
+ * Save meta box custom fields data.
+ *
+ * @param int $post_id
+ * @param WP_Post $post
+ */
+ public static function save($post_id, $post)
+ {
+ // Check the nonce (again).
+ if ( empty( $_POST['woocommerce_meta_nonce'] ) || ! wp_verify_nonce( $_POST['woocommerce_meta_nonce'], 'woocommerce_save_data' ) ) {
+ return;
+ }
+ $coupon = new WC_Coupon( $post_id );
+ $coupon->update_meta_data('cycle_count', intval($_POST['cycle_count']));
+ $coupon->save();
+ }
+
+ /**
+ * Remove Woocommerce Subscriptions recurring discount options.
+ * This is done to force the user to select a vindi cicle count discount
+ *
+ * @param int $post_id
+ * @param WP_Post $post
+ */
+ public static function remove_ws_recurring_discount($discount_types)
+ {
+ return array_diff(
+ $discount_types,
+ array(
+ 'sign_up_fee' => __( 'Sign Up Fee Discount', 'woocommerce-subscriptions' ),
+ 'sign_up_fee_percent' => __( 'Sign Up Fee % Discount', 'woocommerce-subscriptions' ),
+ 'recurring_fee' => __( 'Recurring Product Discount', 'woocommerce-subscriptions' ),
+ 'recurring_percent' => __( 'Recurring Product % Discount', 'woocommerce-subscriptions' ),
+ )
+ );
+ }
+}
diff --git a/src/includes/admin/ProductStatus.php b/src/includes/admin/ProductStatus.php
new file mode 100644
index 0000000..d1e3723
--- /dev/null
+++ b/src/includes/admin/ProductStatus.php
@@ -0,0 +1,33 @@
+vindi_settings = $vindi_settings;
+
+ add_action('admin_notices', array(&$this, 'product_status_notifier'));
+ }
+
+ /**
+ * Show product creation status
+ * @return string $text
+ */
+ public function product_status_notifier()
+ {
+ if(empty(get_transient('vindi_product_message')))
+ return;
+
+ include_once VINDI_SRC . 'views/product-status.php';
+ }
+}
diff --git a/src/includes/admin/Settings.php b/src/includes/admin/Settings.php
index a986192..2227420 100644
--- a/src/includes/admin/Settings.php
+++ b/src/includes/admin/Settings.php
@@ -8,6 +8,11 @@ class VindiSettings extends WC_Settings_API
**/
public $woocommerce;
+ /**
+ * @var VindiDependencies
+ **/
+ public $dependencies;
+
/**
* @var string
**/
@@ -18,30 +23,63 @@ class VindiSettings extends WC_Settings_API
**/
private $plugin;
+ /**
+ * @var VindiLogger
+ **/
+ public $logger;
+
+ /**
+ * @var VindiApi
+ **/
+ public $api;
+
+ /**
+ * @var VindiRoutes
+ **/
+ public $routes;
+
/**
* @var boolean
**/
private $debug;
+ /**
+ * @var boolean
+ **/
+ private $invalidApiKey;
- public function __construct()
+ function __construct()
{
global $woocommerce;
-
+
$this->token = sanitize_file_name(wp_hash(VINDI));
-
+
$this->init_settings();
$this->init_form_fields();
-
+
+ $this->debug = $this->get_option('debug') == 'yes' ? true : false;
+ $this->logger = new VindiLogger(VINDI, $this->debug);
+ $this->api = new VindiApi($this->get_api_key(), $this->logger, $this->get_is_active_sandbox());
+ $this->routes = new VindiRoutes($this);
$this->woocommerce = $woocommerce;
-
-
+ $this->dependencies = new VindiDependencies;
+ $this->invalidApiKey = get_option( 'vindi_invalid_api_key', false );
+
if (is_admin()) {
-
+
+
add_filter('woocommerce_settings_tabs_array', array($this, 'add_settings_tab'), 50);
add_action('woocommerce_settings_tabs_settings_vindi', array(&$this, 'settings_tab'));
- add_action('woocommerce_update_options_settings_vindi', array(&$this, 'process_admin_options'));
- // add_action('woocommerce_update_options_shipping_methods', array(&$this, 'process_admin_options'));
+ add_action('woocommerce_update_options_settings_vindi', array(&$this, 'process_admin_options'), 10);
+ add_action('woocommerce_update_options_settings_vindi', array($this, 'api_key_field'), 11);
+ add_action('woocommerce_settings_tabs_settings_vindi', array($this, 'is_api_key_valid'));
+
+ /**
+ * Add custom input fields in coupon 'General' tab
+ */
+ add_action('woocommerce_coupon_options', 'CouponsMetaBox::output', 40, 2);
+ add_action('woocommerce_coupon_options_save', 'CouponsMetaBox::save', 10, 2);
+ add_action('woocommerce_coupon_discount_types', 'CouponsMetaBox::remove_ws_recurring_discount', 10, 1);
}
}
@@ -63,25 +101,6 @@ public function settings_tab()
$this->get_template('admin-settings.html.php', array('settings' => $this));
}
- /**
- * Initialise Gateway Settings Form Fields
- */
- function init_form_fields()
- {
-
-
- $prospects_url = '' . __('Don\'t have an account?', VINDI) . ' ';
-
- $this->form_fields = array(
- 'api_key' => array(
- 'title' => __('Vindi API key', VINDI),
- 'type' => 'text',
- 'description' => __('The API Key for your Vindi account. ' . $prospects_url, VINDI),
- 'default' => '',
- ),
- );
- }
-
/**
* WC Get Template helper.
*
@@ -102,6 +121,80 @@ public function get_template($name, $args = array())
);
}
+ /**
+ * Initialize Gateway Settings Form Fields
+ */
+ function init_form_fields()
+ {
+ $url = admin_url(sprintf('admin.php?page=wc-status&tab=logs&log_file=%s-%s-log', VINDI, $this->get_token()));
+ $logs_url = '' . __('Ver Logs', VINDI) . ' ';
+ $nfe_know_more = '' . __('Saiba mais', VINDI) . ' ';
+
+ $prospects_url = '' . __('Não possui uma conta?', VINDI) . ' ';
+
+ $sand_box_article = '' . __('Dúvidas?', VINDI) . ' ';
+
+ $this->form_fields = array(
+ 'api_key' => array(
+ 'title' => __('Chave da API Vindi', VINDI),
+ 'type' => $this->checkKey(),
+ 'description' => __('A Chave da API de sua conta na Vindi. ' . $prospects_url, VINDI),
+ 'default' => '',
+ ),
+ 'send_nfe_information' => array(
+ 'title' => __('Emissão de NFe\'s', VINDI),
+ 'label' => __('Enviar informações para emissão de NFe\'s', VINDI),
+ 'type' => 'checkbox',
+ 'description' => sprintf(__('Envia informações de RG e Inscrição Estadual para Emissão de NFe\'s com nossos parceiros. %s', VINDI), $nfe_know_more),
+ 'default' => 'no',
+ ),
+ 'return_status' => array(
+ 'title' => __('Status de conclusão do pedido', VINDI),
+ 'type' => 'select',
+ 'description' => __('Status que o pedido deverá ter após receber a confirmação de pagamento da Vindi.', VINDI),
+ 'default' => 'processing',
+ 'options' => array(
+ 'processing' => 'Processando',
+ 'on-hold' => 'Aguardando',
+ 'completed' => 'Concluído',
+ ),
+ ),
+ 'vindi_synchronism' => array(
+ 'title' => __('Sincronismo de Status das Assinaturas', VINDI),
+ 'type' => 'checkbox',
+ 'label' => __('Enviar alterações de status nas assinaturas do WooCommerce', VINDI),
+ 'description' => __('Envia as alterações de status nas assinaturas do WooCommerce para Vindi.', VINDI),
+ 'default' => 'no',
+ ),
+ 'shipping_and_tax_config' => array(
+ 'title' => __('Cobrança única', VINDI),
+ 'type' => 'checkbox',
+ 'label' => __('Ativar cobrança única para fretes e taxas', VINDI),
+ 'description' => __('Fretes e Taxas serão cobrados somente no primeiro ciclo de uma assinatura', VINDI),
+ 'default' => 'no',
+ ),
+ 'testing' => array(
+ 'title' => __('Testes', VINDI),
+ 'type' => 'title',
+ ),
+ 'sandbox' => array(
+ 'title' => __('Ambiente Sandbox', VINDI),
+ 'label' => __('Ativar Sandbox', VINDI),
+ 'type' => 'checkbox',
+ 'description' => __('Ative esta opção para habilitar a comunicação com o ambiente Sandbox da Vindi.', VINDI),
+ 'default' => 'no',
+ ),
+ 'debug' => array(
+ 'title' => __('Log de Depuração', VINDI),
+ 'label' => __('Ativar Logs', VINDI),
+ 'type' => 'checkbox',
+ 'description' => sprintf(__('Ative esta opção para habilitar logs de depuração do servidor. %s', VINDI), $logs_url),
+ 'default' => 'no',
+ ),
+ );
+ }
+
+
/**
* Get Uniq Token Access
*
@@ -112,31 +205,125 @@ public function get_token()
return $this->token;
}
+ /**
+ * Ocult valid token
+ *
+ * @return string
+ **/
+ function checkKey()
+ {
+
+ return 'text';
+ }
+
/**
* Get Vindi API Key
* @return string
**/
public function get_api_key()
{
- // if ('yes' === $this->get_is_active_sandbox()) {
- // return $this->settings['api_key_sandbox'];
- // }
-
return $this->settings['api_key'];
}
+ /**
+ * Return
+ * @return boolean
+ **/
+ public function get_is_active_sandbox()
+ {
+ return $this->settings['sandbox'];
+ }
/**
- * Check if SSL is enabled when merchant is not trial.
+ * Check if SSL is enabled when merchant is not in sandbox.
* @return boolean
*/
- public static function check_ssl()
+ public function check_ssl()
{
-
- if (WC_Vindi_Payment::MODE != 'development') {
- return false;
+ if ($this->get_is_active_sandbox()) {
+ return true;
} else {
return is_ssl();
}
}
+
+ /**
+ * Validate API key field
+ * @param string $text
+ * @return string $text
+ */
+
+ public function api_key_field()
+ {
+ $api_key = $this->get_api_key();
+ $this->api = new VindiApi($api_key, $this->logger, $this->get_is_active_sandbox());
+
+ if (!$api_key) {
+ return;
+ }
+ if ('unauthorized' === $this->api->test_api_key($api_key)) {
+ update_option('vindi_invalid_api_key', true);
+ $this->invalidApiKey = true;
+
+ include_once VINDI_SRC . 'views/invalid-api-key.php';
+ } else {
+ update_option('vindi_invalid_api_key', false);
+ $this->invalidApiKey = false;
+ }
+ }
+
+ /**
+ * Check if invalidApiKey is true
+ * @return mixed includes the invalid-api-key view
+ */
+ public function is_api_key_valid()
+ {
+ if($this->invalidApiKey) {
+ include_once VINDI_SRC . 'views/invalid-api-key.php';
+ }
+ }
+
+ /**
+ * Get Vindi Shipping and Tax config
+ * @return string
+ */
+ public function get_shipping_and_tax_config()
+ {
+ return 'yes' === $this->settings['shipping_and_tax_config'];
+ }
+
+ public function get_return_status()
+ {
+ if(isset($this->settings['return_status'])) {
+ return $this->settings['return_status'];
+ } else {
+ return 'processing';
+ }
+ }
+
+ public function get_webhooks_url() {
+ return sprintf('%s/index.php/wc-api/%s?token=%s',
+ get_site_url(),
+ WC_Vindi_Payment::WC_API_CALLBACK,
+ $this->get_token()
+ );
+ }
+
+ /**
+ * Get Vindi Synchronism status
+ * @return string
+ */
+ public function get_synchronism_status()
+ {
+ return 'yes' === $this->settings['vindi_synchronism'];
+ }
+
+ /**
+ * Verify if the user wants to send nfe information
+ * @return bool
+ */
+ public function send_nfe_information()
+ {
+ return 'yes' === $this->settings['send_nfe_information'];
+ }
}
diff --git a/src/includes/gateways/BankSlipPayment.php b/src/includes/gateways/BankSlipPayment.php
new file mode 100644
index 0000000..ac0d742
--- /dev/null
+++ b/src/includes/gateways/BankSlipPayment.php
@@ -0,0 +1,137 @@
+id = 'vindi-bank-slip';
+ $this->icon = apply_filters('vindi_woocommerce_bank_slip_icon', '');
+ $this->method_title = __('Vindi - Boleto Bancário', VINDI);
+ $this->method_description = __('Aceitar pagamentos via boleto bancário utilizando a Vindi.', VINDI);
+ $this->has_fields = true;
+
+ $this->supports = array(
+ 'subscriptions',
+ 'products',
+ 'subscription_cancellation',
+ 'subscription_reactivation',
+ 'subscription_suspension',
+ 'subscription_amount_changes',
+ 'subscription_payment_method_change',
+ 'subscription_payment_method_change_customer',
+ 'subscription_payment_method_change_admin',
+ 'subscription_date_changes',
+ 'multiple_subscriptions',
+ 'pre-orders'
+ );
+
+ $this->init_form_fields();
+
+ // Load the settings.
+ $this->init_settings();
+
+ add_action('woocommerce_view_order', array(&$this, 'show_bank_slip_download'), -10, 1);
+ add_action('woocommerce_thankyou_' . $this->id, array(&$this, 'thank_you_page'));
+
+ parent::__construct($vindi_settings, $controllers);
+ $this->title = $this->get_option('title');
+ $this->description = $this->get_option('description');
+ $this->enabled = $this->get_option('enabled');
+
+ }
+
+ /**
+ * Should return payment type for payment processing.
+ * @return string
+ */
+ public function type()
+ {
+ return 'bank_slip';
+ }
+
+ public function init_form_fields()
+ {
+
+ $this->form_fields = array(
+ 'enabled' => array(
+ 'title' => __('Habilitar/Desabilitar', VINDI),
+ 'label' => __('Habilitar pagamento por Boleto Bancário com Vindi', VINDI),
+ 'type' => 'checkbox',
+ 'default' => 'no',
+ ),
+ 'title' => array(
+ 'title' => __('Título', VINDI),
+ 'type' => 'text',
+ 'description' => __('Título que o cliente verá durante o processo de pagamento.', VINDI),
+ 'default' => __('Boleto Bancário', VINDI),
+ )
+ );
+ }
+
+ public function payment_fields()
+ {
+ $user_country = $this->get_country_code();
+
+ if (empty($user_country)) {
+ _e('Selecione o País para visualizar as formas de pagamento.', VINDI);
+ return;
+ }
+
+ if (!$this->routes->acceptBankSlip()) {
+ _e('Este método de pagamento não é aceito.', VINDI);
+ return;
+ }
+
+ $is_single_order = $this->is_single_order();
+
+ if ($is_trial = $this->vindi_settings->get_is_active_sandbox())
+ $is_trial = $this->routes->isMerchantStatusTrialOrSandbox();
+
+ $this->vindi_settings->get_template('bankslip-checkout.html.php', compact('is_trial', 'is_single_order'));
+ }
+
+ public function thank_you_page($order_id)
+ {
+ $order = wc_get_order($order_id);
+ if ($order->get_payment_method() == 'vindi-bank-slip') {
+ $vindi_order = get_post_meta($order_id, 'vindi_order', true);
+ $this->vindi_settings->get_template('bankslip-download.html.php', compact('vindi_order'));
+ }
+ }
+
+ public function show_bank_slip_download($order_id) {
+ $order = wc_get_order($order_id);
+ if ($order->get_payment_method() == 'vindi-bank-slip') {
+ $vindi_order = get_post_meta($order_id, 'vindi_order', true);
+ if(!$order->is_paid() && !$order->has_status('cancelled')) {
+ $this->vindi_settings->get_template('bankslip-download.html.php', compact('vindi_order'));
+ }
+ }
+ }
+}
diff --git a/src/includes/gateways/CreditPayment.php b/src/includes/gateways/CreditPayment.php
index 0c6e713..93af510 100644
--- a/src/includes/gateways/CreditPayment.php
+++ b/src/includes/gateways/CreditPayment.php
@@ -14,22 +14,36 @@
class VindiCreditGateway extends VindiPaymentGateway
{
+ /**
+ * @var VindiSettings
+ */
+ public $vindi_settings;
/**
- * Constructor for the gateway.
+ * @var VindiControllers
*/
+ public $controllers;
- public function __construct()
+ /**
+ * @var int
+ */
+ private $max_installments = 12;
+
+ /**
+ * @var int
+ */
+ public $interest_rate;
+
+ public function __construct(VindiSettings $vindi_settings, VindiControllers $controllers)
{
global $woocommerce;
$this->id = 'vindi-credit-card';
$this->icon = apply_filters('vindi_woocommerce_credit_card_icon', '');
- $this->method_title = __('Vindi - Credit card', 'vindi-woocommerce');
- $this->method_description = __('Accept credit card payments using Vindi.', 'vindi-woocommerce');
+ $this->method_title = __('Vindi - Cartão de Crédito', VINDI);
+ $this->method_description = __('Aceitar pagamentos via cartão de crédito utilizando a Vindi.', VINDI);
$this->has_fields = true;
- $this->view_transaction_url = 'https://google.com.br';
$this->supports = array(
'subscriptions',
@@ -48,203 +62,220 @@ public function __construct()
);
$this->init_form_fields();
-
- // Load the settings.
$this->init_settings();
- $this->title = $this->get_option('title');
- $this->description = $this->get_option('description');
- $this->enabled = $this->get_option('enabled');
- $this->testmode = 'yes' === $this->get_option('testmode');
- $this->private_key = $this->testmode ? $this->get_option('test_private_key') : $this->get_option('private_key');
- $this->publishable_key = $this->testmode ? $this->get_option('test_publishable_key') : $this->get_option('publishable_key');
-
- // This action hook saves the settings
- add_action('woocommerce_update_options_payment_gateways_' . $this->id, array($this, 'process_admin_options'));
+ $this->smallest_installment = $this->get_option('smallest_installment');
+ $this->installments = $this->get_option('installments');
+ $this->verify_method = $this->get_option('verify_method');
+ $this->enable_interest_rate = $this->get_option('enable_interest_rate');
+ $this->interest_rate = $this->get_option('interest_rate');
- // We need custom JavaScript to obtain a token
- add_action('wp_enqueue_scripts', array($this, 'payment_scripts'));
+ parent::__construct($vindi_settings, $controllers);
}
+ /**
+ * Should return payment type for payment processing.
+ * @return string
+ */
+ public function type()
+ {
+ return 'cc';
+ }
+
public function init_form_fields()
{
$this->form_fields = array(
'enabled' => array(
- 'title' => 'Enable/Disable',
- 'label' => 'Enable Misha Gateway',
- 'type' => 'checkbox',
- 'description' => '',
- 'default' => 'no'
+ 'title' => __('Habilitar/Desabilitar', VINDI),
+ 'label' => __('Habilitar pagamento via Cartão de Crédito com a Vindi', VINDI),
+ 'type' => 'checkbox',
+ 'default' => 'no',
),
'title' => array(
- 'title' => 'Title',
+ 'title' => __('Título', VINDI),
'type' => 'text',
- 'description' => 'This controls the title which the user sees during checkout.',
- 'default' => 'Credit Card',
- 'desc_tip' => true,
+ 'description' => __('Título que o cliente verá durante o processo de pagamento.', VINDI),
+ 'default' => __('Cartão de Crédito', VINDI),
),
- 'description' => array(
- 'title' => 'Description',
- 'type' => 'textarea',
- 'description' => 'This controls the description which the user sees during checkout.',
- 'default' => 'Pay with your credit card via our super-cool payment gateway.',
- ),
- 'testmode' => array(
- 'title' => 'Test mode',
- 'label' => 'Enable Test Mode',
+ 'verify_method' => array(
+ 'title' => __('Transação de Verificação', VINDI),
'type' => 'checkbox',
- 'description' => 'Place the payment gateway in test mode using test API keys.',
- 'default' => 'yes',
- 'desc_tip' => true,
+ 'description' => __(' Realiza a transação de verificação em todos os novos pedidos. (Taxas adicionais por verificação poderão ser cobradas).', VINDI),
+ 'default' => 'no',
+ ),
+ 'single_charge' => array(
+ 'title' => __('Vendas Avulsas', VINDI),
+ 'type' => 'title',
),
- 'test_publishable_key' => array(
- 'title' => 'Test Publishable Key',
- 'type' => 'text'
+ 'smallest_installment' => array(
+ 'title' => __('Valor mínimo da parcela', VINDI),
+ 'type' => 'text',
+ 'description' => __('Valor mínimo da parcela, não deve ser inferior a R$ 5,00.', VINDI),
+ 'default' => '5',
),
- 'test_private_key' => array(
- 'title' => 'Test Private Key',
- 'type' => 'password',
+ 'installments' => array(
+ 'title' => __('Número máximo de parcelas', VINDI),
+ 'type' => 'select',
+ 'description' => __('Número máximo de parcelas para vendas avulsas. Deixe em 1x para desativar o parcelamento.', VINDI),
+ 'default' => '1',
+ 'options' => array(
+ '1' => '1x',
+ '2' => '2x',
+ '3' => '3x',
+ '4' => '4x',
+ '5' => '5x',
+ '6' => '6x',
+ '7' => '7x',
+ '8' => '8x',
+ '9' => '9x',
+ '10' => '10x',
+ '11' => '11x',
+ '12' => '12x',
+ ),
),
- 'publishable_key' => array(
- 'title' => 'Live Publishable Key',
- 'type' => 'text'
+ 'enable_interest_rate' => array(
+ 'title' => __('Habilitar juros', VINDI),
+ 'type' => 'checkbox',
+ 'description' => __('Habilitar juros no parcelamento do pedido.', VINDI),
+ 'default' => 'no',
),
- 'private_key' => array(
- 'title' => 'Live Private Key',
- 'type' => 'password'
+ 'interest_rate' => array(
+ 'title' => __('Taxa de juros ao mês (%)', VINDI),
+ 'type' => 'text',
+ 'description' => __('Taxa de juros que será adicionada aos pagamentos parcelados.', VINDI),
+ 'default' => '0.1',
)
);
}
- public function payment_scripts()
+ public function payment_fields()
{
-
- // we need JavaScript to process a token only on cart/checkout pages, right?
- if (!is_cart() && !is_checkout() && !isset($_GET['pay_for_order'])) {
- return;
+ $id = $this->id;
+ $is_trial = $this->is_trial;
+
+ $cart = $this->vindi_settings->woocommerce->cart;
+ $total = $cart->total;
+ foreach ($cart->get_fees() as $index => $fee) {
+ if($fee->name == __('Juros', VINDI)) {
+ $total -= $fee->amount;
+ }
}
- // if our payment gateway is disabled, we do not have to enqueue JS too
- if ('no' === $this->enabled) {
- return;
+ $max_times = 12;
+ $max_times = $this->get_order_max_installments($total);
+
+ if ($max_times > 1) {
+ for ($times = 1; $times <= $max_times; $times++) {
+ if ($this->is_interest_rate_enabled()) {
+ $installments[$times] = ($total * (1 + (($this->get_interest_rate() / 100) * ($times - 1)))) / $times;
+ } else {
+ $installments[$times] = ceil($total / $times * 100) / 100;
+ }
+ }
}
- // no reason to enqueue JavaScript if API keys are not set
- if (empty($this->private_key) || empty($this->publishable_key)) {
- return;
- }
+ $user_payment_profile = $this->build_user_payment_profile();
+ $payment_methods = $this->routes->getPaymentMethods();
- // do not work with card detailes without SSL unless your website is in a test mode
- if (!$this->testmode && !is_ssl()) {
+ if ($payment_methods === false || empty($payment_methods) || ! count($payment_methods['credit_card'])) {
+ _e('Estamos enfrentando problemas técnicos no momento. Tente novamente mais tarde ou entre em contato.', VINDI);
return;
}
- // let's suppose it is our payment processor JavaScript that allows to obtain a token
- wp_enqueue_script('misha_js', 'https://www.mishapayments.com/api/token.js');
+ if ($is_trial = $this->vindi_settings->get_is_active_sandbox())
+ $is_trial = $this->routes->isMerchantStatusTrialOrSandbox();
- // and this is our custom JS in your plugin directory that works with token.js
- wp_register_script('woocommerce_misha', plugins_url('misha.js', __FILE__), array('jquery', 'misha_js'));
-
- // in most payment processors you have to use PUBLIC KEY to obtain a token
- wp_localize_script('woocommerce_misha', 'misha_params', array(
- 'publishableKey' => $this->publishable_key
+ $this->vindi_settings->get_template('creditcard-checkout.html.php', compact(
+ 'installments',
+ 'is_trial',
+ 'user_payment_profile',
+ 'payment_methods'
));
-
- wp_enqueue_script('woocommerce_misha');
}
- public function payment_fields()
+ public function verify_user_payment_profile()
{
+ $old_payment_profile = (int) filter_input(
+ INPUT_POST,
+ 'vindi-old-cc-data-check',
+ FILTER_SANITIZE_NUMBER_INT
+ );
- // ok, let's display some description before the payment form
- if ($this->description) {
- // you can instructions for test mode, I mean test card numbers etc.
- if ($this->testmode) {
- $this->description .= ' TEST MODE ENABLED. In test mode, you can use the card numbers listed in documentation .';
- $this->description = trim($this->description);
- }
- // display the description with tags etc.
- echo wpautop(wp_kses_post($this->description));
- }
-
- // I will echo() the form, but you can close PHP tags and print it directly in HTML
- echo '
';
-
- // Add this action hook if you want your custom payment gateway to support it
- do_action('woocommerce_credit_card_form_start', $this->id);
-
- // I recommend to use inique IDs, because other gateways could already use #ccNo, #expdate, #cvc
- echo 'Card Number *
-
-
-
- Expiry Date *
-
-
-
- Card Code (CVC) *
-
-
-
';
-
- do_action('woocommerce_credit_card_form_end', $this->id);
-
- echo '
';
+ return 1 === $old_payment_profile;
}
-
- public function process_payment($order_id)
+ public function verify_method()
{
+ return 'yes' === $this->verify_method;
+ }
- print_r($order_id);
-
- global $woocommerce;
-
- // we need it to get any order detailes
- $order = wc_get_order($order_id);
+ public function is_interest_rate_enabled()
+ {
+ return 'yes' === $this->enable_interest_rate;
+ }
+ public function get_interest_rate()
+ {
+ return floatval($this->interest_rate);
+ }
- /*
- * Array with parameters for API interaction
- */
- $args = array();
+ protected function get_order_max_installments($order_total)
+ {
+ if($this->is_single_order()) {
+ $order_max_times = floor($order_total / $this->smallest_installment);
+ $max_times = empty($order_max_times) ? 1 : $order_max_times;
- /*
- * Your API interaction could be built with wp_remote_post()
- */
- $response = wp_remote_post('{payment processor endpoint}', $args);
+ return min($this->max_installments, $max_times, $this->get_installments());
+ }
+ return $this->get_installments();
+ }
- if (!is_wp_error($response)) {
+ private function build_user_payment_profile()
+ {
+ $user_payment_profile = array();
+ $user_vindi_id = get_user_meta(wp_get_current_user()->ID, 'vindi_customer_id', true);
+ $payment_profile = WC()->session->get('current_payment_profile');
+ $current_customer = WC()->session->get('current_customer');
- $body = json_decode($response['body'], true);
+ if (!isset($payment_profile) || $current_customer['code'] != $user_vindi_id) {
+ $payment_profile = $this->routes->getPaymentProfile($user_vindi_id);
+ }
- // it could be different depending on your payment processor
- if ($body['response']['responseCode'] == 'APPROVED') {
+ if($payment_profile['type'] !== 'PaymentProfile::CreditCard')
+ return $user_payment_profile;
- // we received the payment
- $order->payment_complete();
- $order->reduce_order_stock();
+ if(false === empty($payment_profile)) {
+ $user_payment_profile['holder_name'] = $payment_profile['holder_name'];
+ $user_payment_profile['payment_company'] = $payment_profile['payment_company']['code'];
+ $user_payment_profile['card_number'] = sprintf('**** **** **** %s', $payment_profile['card_number_last_four']);
+ }
- // some notes to customer (replace true with false to make it private)
- $order->add_order_note('Hey, your order is paid! Thank you!', true);
+ WC()->session->set('current_payment_profile', $payment_profile);
+ return $user_payment_profile;
+ }
- // Empty cart
- $woocommerce->cart->empty_cart();
+ protected function get_installments()
+ {
+ if($this->is_single_order())
+ return $this->installments;
- // Redirect to the thank you page
- return array(
- 'result' => 'success',
- 'redirect' => $this->get_return_url($order)
- );
- } else {
- wc_add_notice('Please try again.', 'error');
- return;
- }
- } else {
- wc_add_notice('Connection error.', 'error');
- return;
+ foreach($this->vindi_settings->woocommerce->cart->cart_contents as $item) {
+ $plan_id = $item['data']->get_meta('vindi_subscription_plan');
+ if (!empty($plan_id))
+ break;
}
+
+ $current_plan = WC()->session->get('current_plan');
+ if ($current_plan && $current_plan['id'] == $plan_id && !empty($current_plan['installments']))
+ return $current_plan['installments'];
+
+ $plan = $this->routes->getPlan($plan_id);
+ WC()->session->set('current_plan', $plan);
+ if($plan['installments'] > 1)
+ return $plan['installments'];
+
+ return 1;
}
}
diff --git a/src/routes/RoutesApi.php b/src/routes/RoutesApi.php
new file mode 100644
index 0000000..323f0d4
--- /dev/null
+++ b/src/routes/RoutesApi.php
@@ -0,0 +1,496 @@
+
+vindi_settings = $vindi_settings;
+ $this->api = $this->vindi_settings->api;
+ }
+
+ /**
+ * Enough if there is a product within Vindi
+ *
+ * @since 1.0.0
+ * @version 1.0.0
+ * @return array
+ */
+ public function findProductById($product_id)
+ {
+
+ $response = $this->api->request(sprintf(
+ 'products/%s',
+ $product_id
+ ), 'GET');
+
+ $productExists = isset($response['product']['id']) ? $response['product'] : false;
+
+ return $productExists;
+ }
+
+
+ /**
+ * Post method for creating plan in the Vindi
+ *
+ * @since 1.0.0
+ * @version 1.0.0
+ * @return array
+ */
+ public function createPlan($data)
+ {
+
+ $response = $this->api->request('plans', 'POST', $data);
+
+ return $response['plan'];
+ }
+
+ /**
+ * Update method for updating plan in the Vindi
+ *
+ * @since 1.0.0
+ * @version 1.0.0
+ * @return array
+ */
+ public function updatePlan($plan_id, $data)
+ {
+
+ $response = $this->api->request(sprintf(
+ 'plans/%s',
+ $plan_id
+ ), 'PUT', $data);
+
+ return $response['plan'];
+ }
+
+ /**
+ * Post method for creating product in the Vindi
+ *
+ * @since 1.0.0
+ * @version 1.0.0
+ * @return array
+ */
+ public function createProduct($data)
+ {
+
+ $response = $this->api->request('products', 'POST', $data);
+ return $response['product'];
+ }
+
+ /**
+ * Update method for updating product in the Vindi
+ *
+ * @since 1.0.0
+ * @version 1.0.0
+ * @return array
+ */
+ public function updateProduct($product_id, $data)
+ {
+
+ $response = $this->api->request(sprintf(
+ 'products/%s',
+ $product_id
+ ), 'PUT', $data);
+ return $response['product'];
+ }
+
+ /**
+ * Post method for creating customer in the Vindi
+ *
+ * @since 1.0.0
+ * @version 1.0.0
+ * @return array
+ */
+ public function createCustomer($data)
+ {
+
+ $response = $this->api->request('customers', 'POST', $data);
+ return $response['customer'];
+ }
+
+ /**
+ * Update method for update profile customer in the Vindi
+ *
+ * @since 1.0.0
+ * @version 1.0.0
+ * @return array
+ */
+ public function updateCustomer($user_id, $data)
+ {
+
+ $response = $this->api->request(sprintf(
+ 'customers/%s',
+ $user_id
+ ), 'PUT', $data);
+
+ return $response['customer'];
+ }
+
+ /**
+ * Delete method to disable the customer in the Vindi
+ *
+ * @since 1.0.0
+ * @version 1.0.0
+ * @return array
+ */
+ public function deleteCustomer($user_id)
+ {
+
+ $response = $this->api->request(sprintf(
+ 'customers/%s',
+ $user_id
+ ), 'DELETE');
+
+ return $response['customer'];
+ }
+
+
+ /**
+ * Check if exists user in Vindi
+ *
+ * @since 1.0.0
+ * @version 1.0.0
+ * @return array
+ */
+ public function findCustomerById($id)
+ {
+
+ $response = $this->api->request(sprintf(
+ 'customers/%s',
+ $id
+ ), 'GET');
+
+ $userExists = isset($response['customer']['id']) ? $response['customer'] : false;
+
+ return $userExists;
+ }
+
+ /**
+ * @param $data (plan_id, customer_id, payment_method_code, product_items[{product_id}])
+ *
+ * @return array
+ */
+ public function createSubscription($data)
+ {
+ if (($response = $this->api->request('subscriptions', 'POST', $data)) &&
+ isset($response['subscription']['id'])) {
+
+ $subscription = $response['subscription'];
+ $subscription['bill'] = $response['bill'];
+
+ return $subscription;
+ }
+
+ return false;
+ }
+
+ /**
+ * @param int $subscription_id
+ * @param bool $cancel_bills
+ *
+ * @return array|bool|mixed
+ */
+ public function suspendSubscription($subscription_id, $cancel_bills = false)
+ {
+ $query = '';
+
+ if(!$cancel_bills)
+ $query = '?cancel_bills=false';
+
+ $response = $this->api->request(
+ sprintf('subscriptions/%s%s', $subscription_id, $query), 'DELETE');
+
+ return $response;
+ }
+
+ /**
+ * @param int $subscription_id
+ *
+ * @return array|bool|mixed
+ */
+ public function activateSubscription($subscription_id)
+ {
+ if ($response = $this->api->request('subscriptions/' . $subscription_id . '/reactivate', 'POST'))
+ return $response;
+
+ return false;
+ }
+
+ /**
+ * @param int $subscription_id
+ *
+ * @return array|bool|mixed
+ */
+ public function getSubscription($subscription_id)
+ {
+ if ($response = $this->api->request("subscriptions/{$subscription_id}",'GET')['subscription'])
+ return $response;
+
+ return false;
+ }
+
+ /**
+ * @param string $subscription_id
+ *
+ * @return bool
+ */
+ public function isSubscriptionActive($subscription_id)
+ {
+ if (isset($this->recentRequest)
+ && $this->recentRequest['id'] == $subscription_id) {
+ if ($this->recentRequest['status'] != 'canceled')
+ return true;
+ return false;
+ }
+
+ $response = $this->getSubscription($subscription_id);
+
+ if ($response && array_key_exists('status', $response)) {
+ if ($response['status'] != 'canceled') {
+ $this->recentRequest = $response;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public function verifyCustomerPaymentProfile($payment_profile_id)
+ {
+ return 'success' === $this->api->request(sprintf(
+ 'payment_profiles/%s/verify',
+ $payment_profile_id
+ ), 'POST')['transaction']['status'];
+ }
+
+ public function createCustomerPaymentProfile($data)
+ {
+ // Protect credit card number.
+ $log = $data;
+ $log['card_number'] = sprintf('**** *%s', substr($log['card_number'], -3));
+ $log['card_cvv'] = '***';
+
+ $response = $this->api->request('payment_profiles', 'POST', $data, $log);
+
+ return $response['payment_profile'];
+ }
+
+ public function findProductByCode($code)
+ {
+ $transient_key = "vindi_product_{$code}";
+ $product = get_transient($transient_key);
+
+ if(false !== $product)
+ return $product;
+
+ $response = $this->api->request(sprintf('products?query=code:%s', $code), 'GET');
+
+ if (false === empty($response['products'])) {
+ $product = end($response['products']);
+ set_transient($transient_key, $product, 1 * HOUR_IN_SECONDS);
+ }
+
+ return $product;
+ }
+
+ public function findOrCreateProduct($name, $code)
+ {
+ $product = $this->findProductByCode($code);
+
+ if (false === $product)
+ {
+ return $this->createProduct(array(
+ 'name' => $name,
+ 'code' => $code,
+ 'status' => 'active',
+ 'pricing_schema' => array(
+ 'price' => 0,
+ ),
+ ));
+ }
+
+ return $product;
+ }
+
+ public function createBill($data)
+ {
+ if ($response = $this->api->request('bills', 'POST', $data)) {
+ return $response['bill'];
+ }
+
+ return false;
+ }
+
+ public function deleteBill($bill_id, $comments = '')
+ {
+ $query = '';
+
+ if($comments)
+ $query = '?comments= ' . $comments;
+
+ if ($response = $this->api->request(
+ sprintf('bills/%s%s', $bill_id, $query), 'DELETE')
+ ) {
+ return $response;
+ }
+
+ return false;
+ }
+
+ public function getPaymentMethods()
+ {
+ if (false === ($payment_methods = get_transient('vindi_payment_methods'))) {
+
+ $payment_methods = array(
+ 'credit_card' => array(),
+ 'bank_slip' => false,
+ );
+
+ $response = $this->api->request('payment_methods', 'GET');
+
+ if (false == $response)
+ return false;
+
+ foreach ($response['payment_methods'] as $method) {
+ if ('active' !== $method['status']) {
+ continue;
+ }
+
+ if ('PaymentMethod::CreditCard' === $method['type']) {
+ $payment_methods['credit_card'] = array_merge(
+ $payment_methods['credit_card'],
+ $method['payment_companies']
+ );
+ } else if ('PaymentMethod::BankSlip' === $method['type'] || 'PaymentMethod::OnlineBankSlip' === $method['type']) {
+ $payment_methods['bank_slip'] = true;
+ }
+ }
+
+ set_transient('vindi_payment_methods', $payment_methods, 1 * HOUR_IN_SECONDS);
+ }
+
+ $this->api->accept_bank_slip = $payment_methods['bank_slip'];
+
+ return $payment_methods;
+ }
+
+ public function isMerchantStatusTrialOrSandbox($is_config = false)
+ {
+ if ('yes' === $this->sandbox)
+ return true;
+
+ $merchant = $is_config ? $this->getMerchant($is_config) : $this->getMerchant();
+
+ if ('trial' === $merchant['status'])
+ return true;
+
+ return false;
+ }
+
+ public function getMerchant($is_config = false)
+ {
+ if (false === ($merchant = get_transient('vindi_merchant')) || $is_config) {
+ $response = $this->api->request('merchant', 'GET');
+
+ if (!$response || !$response['merchant'])
+ return false;
+
+ $merchant = $response['merchant'];
+
+ set_transient('vindi_merchant', $merchant, 1 * HOUR_IN_SECONDS);
+ }
+
+ return $merchant;
+ }
+
+ public function getCharge($charge_id)
+ {
+ $response = $this->api->request("charges/{$charge_id}", 'GET');
+
+ if (empty($response['charge']))
+ return false;
+
+ return $response['charge'];
+ }
+
+ public function getPlan($plan_id)
+ {
+ $response = $this->api->request("plans/{$plan_id}", 'GET');
+
+ if (empty($response['plan'])) {
+ $this->current_plan = false;
+ return false;
+ }
+ $this->current_plan = $response['plan'];
+ return $this->current_plan;
+ }
+
+ public function getPaymentProfile($user_vindi_id)
+ {
+ $customer = $this->findCustomerById($user_vindi_id);
+
+ if (empty($customer))
+ return false;
+
+ $query = urlencode("customer_id={$customer['id']} status=active type=PaymentProfile::CreditCard");
+ $response = $this->api->request('payment_profiles?query='.$query, 'GET');
+
+ if (end($response['payment_profiles']) !== null)
+ return end($response['payment_profiles']);
+
+ return false;
+ }
+
+ public function acceptBankSlip()
+ {
+ if (null === $this->api->accept_bank_slip) {
+ $this->getPaymentMethods();
+ }
+
+ return $this->api->accept_bank_slip;
+ }
+
+ /**
+ * Enough if there is a product within Vindi
+ *
+ * @since 1.0.0
+ * @version 1.0.0
+ * @return array
+ */
+ public function findBillById($bill_id)
+ {
+
+ $response = $this->api->request(sprintf(
+ 'bills/%s',
+ $bill_id
+ ), 'GET');
+
+ if (isset($response['bill']))
+ return $response['bill'];
+
+ return false;
+ }
+
+ public function refundCharge($charge_id, $data)
+ {
+ $response = $this->api->request(sprintf('charges/%s/refund', $charge_id), 'POST', $data);
+
+ if (isset($response['charge'])) {
+ return $response['charge'];
+ }
+
+ return false;
+ }
+}
+?>
diff --git a/src/services/Api.php b/src/services/Api.php
new file mode 100644
index 0000000..5d8057c
--- /dev/null
+++ b/src/services/Api.php
@@ -0,0 +1,280 @@
+ 'Número do cartão inválido.',
+ 'invalid_parameter|registry_code' => 'CPF ou CNPJ Invalidos',
+ 'invalid_parameter|payment_company_code' => 'Método de pagamento Inválido',
+ 'invalid_parameter|payment_company_id' => 'Método de pagamento Inválido',
+ 'invalid_parameter|phones.number' => 'Número de telefone inválido',
+ 'invalid_parameter|phones' => 'Erro ao cadastrar o telefone'
+ );
+
+ /**
+ * API Base path
+ *
+ * @return string
+ */
+ public function base_path()
+ {
+ if ('yes' === $this->sandbox) {
+ return 'https://sandbox-app.vindi.com.br/api/v1/';
+ }
+
+ return 'https://app.vindi.com.br/api/v1/';
+ }
+
+ /**
+ * @param string $key
+ * @param VindiLogger $logger
+ * @param string $sandbox
+ */
+ public function __construct($key, VindiLogger $logger, $sandbox)
+ {
+ $this->key = $key;
+ $this->logger = $logger;
+ $this->sandbox = $sandbox;
+ }
+
+ /**
+ * @param array $data
+ *
+ * @return mixed
+ */
+ private function build_body($data)
+ {
+ $body = null;
+
+ if (!empty($data)) {
+ $body = json_encode($data);
+ }
+
+ return $body;
+ }
+
+ /**
+ * @param array $data
+ *
+ * @return mixed
+ */
+ private function convert_body_to_json($data)
+ {
+ $body = null;
+
+ if (!empty($data)) {
+ $body = json_encode($data);
+ }
+
+ return $body;
+ }
+
+ /**
+ * Generate Basic Authentication Header .
+ * @return string
+ */
+ private function get_auth_header()
+ {
+ return sprintf('Basic %s', base64_encode($this->key . ":"));
+ }
+
+ /**
+ * @param array $error
+ *
+ * @return string
+ */
+ private function get_error_message($error)
+ {
+ $error_id = empty($error['id']) ? '' : $error['id'];
+ $error_parameter = empty($error['parameter']) ? '' : $error['parameter'];
+
+ $error_identifier = sprintf('%s|%s', $error_id, $error_parameter);
+
+ if (false === array_key_exists($error_identifier, $this->errors_list))
+ return $error_identifier;
+
+ return __($this->errors_list[$error_identifier], VINDI);
+ }
+
+ /**
+ * @param array $response
+ *
+ * @return bool
+ */
+ private function check_response($response)
+ {
+ if (isset($response['errors']) && !empty($response['errors'])) {
+ foreach ($response['errors'] as $error) {
+ $message = $this->get_error_message($error);
+
+ if (function_exists('wc_add_notice')) {
+ wc_add_notice(__($message, VINDI), 'error');
+ }
+
+ $this->last_error = $message;
+ }
+
+ return false;
+ }
+
+ $this->last_error = '';
+
+ return true;
+ }
+
+ /**
+ * Verify API key authorization and clear
+ * all transient data if access was denied
+ *@param $api_key string
+ *@return mixed|boolean|string
+ */
+ public function test_api_key($api_key)
+ {
+ delete_transient('vindi_merchant');
+
+ $url = $this->base_path() . 'merchant';
+ $method = 'GET';
+ $request_id = rand();
+ $data_to_log = 'API Authorization Test';
+
+ $this->logger->log(sprintf("[Request #%s]: Novo Request para a API.\n%s %s\n%s", $request_id, $method, $url, $data_to_log));
+
+ $response = wp_remote_post($url, [
+ 'headers' => [
+ 'Authorization' => 'Basic ' . base64_encode($api_key . ':'),
+ 'Content-Type' => 'application/json',
+ 'User-Agent' => sprintf('Vindi-WooCommerce/%s; %s', VINDI_VERSION, get_bloginfo('url')),
+ ],
+ 'method' => $method,
+ 'timeout' => 60,
+ 'sslverify' => true,
+ ]);
+
+ if (is_wp_error($response)) {
+ $this->logger->log(sprintf("[Request #%s]: Erro ao fazer request! %s", $request_id, print_r($response, true)));
+
+ return false;
+ }
+
+ $status = $response['response']['code'] . ' ' . $response['response']['message'];
+ $this->logger->log(sprintf("[Request #%s]: Nova Resposta da API.\n%s\n%s", $request_id, $status, print_r($response['body'], true)));
+
+ $response_body = wp_remote_retrieve_body($response);
+
+ if (!$response_body) {
+ $this->logger->log(sprintf('[Request #%s]: Erro ao recuperar corpo do request! %s', $request_id, print_r($response, true)));
+
+ return false;
+ }
+
+ $response_body_array = json_decode($response_body, true);
+
+ if (isset($response_body_array['errors']) && !empty($response_body_array['errors'])) {
+ foreach ($response_body_array['errors'] as $error) {
+ if ('unauthorized' == $error['id'] and 'authorization' == $error['parameter']) {
+ delete_transient('vindi_plans');
+ delete_transient('vindi_payment_methods');
+ $message = $this->get_error_message($error);
+ $this->last_error = $message;
+
+ return $error['id'];
+ }
+ }
+ }
+
+ set_transient('vindi_merchant', $response_body_array['merchant'], 1 * HOUR_IN_SECONDS);
+ return true;
+ }
+
+ /**
+ * @param string $endpoint
+ * @param string $method
+ * @param array $data
+ * @param null $data_to_log
+ *
+ * @return array|bool|mixed
+ */
+ public function request($endpoint, $method = 'POST', $data = array(), $data_to_log = null)
+ {
+ $url = sprintf('%s%s', $this->base_path(), $endpoint);
+ $body = $this->build_body($data);
+
+ $request_id = rand();
+
+ $data_to_log = null !== $data_to_log ? $this->build_body($data_to_log) : $body;
+
+ $this->logger->log(sprintf("[Request #%s]: Novo Request para a API.\n%s %s\n%s", $request_id, $method, $url, $data_to_log));
+
+ $response = wp_remote_post($url, array(
+ 'headers' => array(
+ 'Authorization' => $this->get_auth_header(),
+ 'Content-Type' => 'application/json',
+ 'User-Agent' => sprintf(VINDI . '/%s; %s', VINDI_VERSION, get_bloginfo('url')),
+ ),
+ 'method' => $method,
+ 'timeout' => 60,
+ 'sslverify' => true,
+ 'body' => $body,
+ ));
+
+ if (is_wp_error($response)) {
+ $this->logger->log(sprintf("[Request #%s]: Erro ao fazer request! %s", $request_id, print_r($response, true)));
+
+ return false;
+ }
+
+ $status = sprintf('%s %s', $response['response']['code'], $response['response']['message']);
+ $this->logger->log(sprintf("[Request #%s]: Nova Resposta da API.\n%s\n%s", $request_id, $status, print_r($response['body'], true)));
+
+ $response_body = wp_remote_retrieve_body($response);
+
+ if (!$response_body) {
+ $this->logger->log(sprintf('[Request #%s]: Erro ao recuperar corpo do request! %s', $request_id, print_r($response, true)));
+
+ return false;
+ }
+
+ $response_body_array = json_decode($response_body, true);
+
+ if (!$this->check_response($response_body_array)) {
+ return false;
+ }
+
+ return $response_body_array;
+ }
+}
diff --git a/src/services/Logger.php b/src/services/Logger.php
new file mode 100644
index 0000000..e11ee85
--- /dev/null
+++ b/src/services/Logger.php
@@ -0,0 +1,40 @@
+main_logger = new WC_Logger();
+ $this->identifier = $identifier;
+ $this->is_active = $is_active;
+ }
+
+ /**
+ * @return boolean
+ */
+ public function log($message)
+ {
+ if ($this->is_active) {
+ $this->main_logger->add($this->identifier, $message);
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/src/services/VindiHelpers.php b/src/services/VindiHelpers.php
new file mode 100644
index 0000000..534064d
--- /dev/null
+++ b/src/services/VindiHelpers.php
@@ -0,0 +1,159 @@
+<"'.
+ *
+ * @since 1.0.0
+ * @param string $statement_descriptor
+ * @return string $statement_descriptor Sanitized statement descriptor
+ */
+ public static function clean_statement_descriptor($statement_descriptor = '')
+ {
+ $disallowed_characters = array('<', '>', '"', "'");
+
+ // Remove special characters.
+ $statement_descriptor = str_replace($disallowed_characters, '', $statement_descriptor);
+
+ $statement_descriptor = substr(trim($statement_descriptor), 0, 22);
+
+ return $statement_descriptor;
+ }
+
+ /**
+ * Get Vindi amount to pay
+ *
+ * @param float $total Amount due.
+ * @param string $currency Accepted currency.
+ *
+ * @return float|int
+ */
+
+ public static function get_vindi_amount($total, $currency = '')
+ {
+ if (!$currency) {
+ $currency = get_woocommerce_currency();
+ }
+
+ return absint(wc_format_decimal(((float) $total * 100), wc_get_price_decimals())); // In cents.
+
+ }
+
+ /**
+ * Checks if WC version is less than passed in version.
+ *
+ * @since 1.0.0
+ * @param string $version Version to check against.
+ * @return bool
+ */
+ public static function is_wc_lt($version)
+ {
+ return version_compare(WC_VERSION, $version, '<');
+ }
+
+ /**
+ * Save Woocommerce custom attributes
+ *
+ * @since 1.0.0
+ * @param string $version Version to check against.
+ * @return null
+ */
+
+ public static function wc_post_meta($post_id, $custom_attributes)
+ {
+
+ // Get product
+ $product = wc_get_product($post_id);
+
+ $i = 0;
+
+ // Loop through the attributes array
+ foreach ($custom_attributes as $name => $value) {
+
+ // Check meta value exists
+ $product->update_meta_data($name, $value);
+
+ $i++;
+ }
+
+ $product->save();
+ }
+
+ /**
+ * Get a subscription that has an item equals as an order item, if any.
+ *
+ * @since 1.0.0
+ * @param WC_Order $order A WC_Order object
+ * @param WC_Order_Item_Product $order_item The order item
+ *
+ * @return WC_Subscription
+ */
+ public static function get_matching_subscription($order, $order_item)
+ {
+ $subscriptions = wcs_get_subscriptions_for_order($order, array('order_type' => 'parent'));
+ $matching_subscription = null;
+ foreach ($subscriptions as $subscription) {
+ foreach ($subscription->get_items() as $subscription_item) {
+ $line_item = wcs_find_matching_line_item($order, $subscription_item, $match_type = 'match_attributes');
+ if($order_item === $line_item) {
+ $matching_subscription = $subscription;
+ break 2;
+ }
+ }
+ }
+
+ if (null === $matching_subscription && !empty($subscriptions)) {
+ $matching_subscription = array_pop($subscriptions);
+ }
+
+ return $matching_subscription;
+ }
+
+ /**
+ * Get the subscription item that matches the order item.
+ *
+ * @since 1.0.0
+ * @param WC_Subscription $subscription The WC_Subscription object
+ * @param WC_Order_Item_Product $order_item The order item
+ * @param string $match_type Optional. The type of comparison to make. Can be 'match_product_ids' to compare product|variation IDs or 'match_attributes' to also compare by item attributes on top of matching product IDs. Default 'match_attributes'.
+ *
+ * @return WC_Order_Item_Product|bool
+ */
+ public static function get_matching_subscription_item($subscription, $order_item, $match_type = 'match_attributes')
+ {
+ $matching_item = false;
+
+ if ('match_attributes' === $match_type) {
+ $order_item_attributes = wp_list_pluck($order_item->get_formatted_meta_data('_', true), 'value', 'key');
+ }
+
+ $order_item_canonical_product_id = wcs_get_canonical_product_id($order_item);
+
+ foreach ($subscription->get_items() as $subscription_item) {
+ if (wcs_get_canonical_product_id($subscription_item) !== $order_item_canonical_product_id) {
+ continue;
+ }
+
+ // Check if we have matching meta key and value pairs loosely - they can appear in any order,
+ if ('match_attributes' === $match_type && wp_list_pluck($subscription_item->get_formatted_meta_data('_', true), 'value', 'key') != $order_item_attributes) {
+ continue;
+ }
+
+ $matching_item = $subscription_item;
+ break;
+ }
+
+ return $matching_item;
+ }
+}
diff --git a/src/services/Webhooks.php b/src/services/Webhooks.php
new file mode 100644
index 0000000..19fd09c
--- /dev/null
+++ b/src/services/Webhooks.php
@@ -0,0 +1,477 @@
+vindi_settings = $vindi_settings;
+ $this->routes = $vindi_settings->routes;
+ }
+
+ /**
+ * Handle incoming webhook.
+ */
+ public function handle()
+ {
+ $token = filter_input(INPUT_GET, 'token', FILTER_SANITIZE_STRING);
+ $raw_body = file_get_contents('php://input');
+ $body = json_decode($raw_body);
+
+ if(!$this->validate_access_token($token)) {
+ http_response_code(403);
+ die('invalid access token');
+ }
+
+ $this->vindi_settings->logger->log(sprintf(__('Novo Webhook chamado: %s', VINDI), $raw_body));
+
+ try {
+ $this->process_event($body);
+ } catch (Exception $e) {
+ $this->vindi_settings->logger->log($e->getMessage());
+
+ if(2 === $e->getCode()) {
+ header("HTTP/1.0 422 Unprocessable Entity");
+ die($e->getMessage());
+ }
+ }
+ }
+
+ /**
+ * @param string $token
+ */
+ private function validate_access_token($token)
+ {
+ return $token === $this->vindi_settings->get_token();
+ }
+
+ /**
+ * Read json entity received and proccess the right event
+ * @param string $body
+ */
+ private function process_event($body)
+ {
+ if(null == $body || empty($body->event))
+ throw new Exception(__('Falha ao interpretar JSON do webhook: Evento do Webhook não encontrado!', VINDI));
+
+ $type = $body->event->type;
+ $data = $body->event->data;
+
+ if(method_exists($this, $type)) {
+ $this->vindi_settings->logger->log(sprintf(__('Novo Evento processado: %s', VINDI), $type));
+ return $this->{$type}($data);
+ }
+
+ $this->vindi_settings->logger->log(sprintf(__('Evento do webhook ignorado pelo plugin: ', VINDI), $type));
+ }
+
+ /**
+ * Process test event from webhook
+ * @param $data array
+ */
+ private function test($data)
+ {
+ $this->vindi_settings->logger->log(__('Evento de teste do webhook.', VINDI));
+ }
+
+ /**
+ * Process subscription_renew event from webhook
+ * @param $renew_infos array
+ */
+ private function subscription_renew($renew_infos)
+ {
+ $subscription = $this->find_subscription_by_id($renew_infos['wc_subscription_id']);
+
+ if($this->subscription_has_order_in_cycle($renew_infos['vindi_subscription_id'], $renew_infos['cycle'])) {
+ throw new Exception(sprintf(
+ __('Já existe o ciclo %s para a assinatura #%s pedido #%s!', VINDI),
+ $renew_infos['cicle'],
+ $renew_infos['vindi_subscription_id'],
+ $subscription->get_last_order()
+ ));
+ }
+
+ WC_Subscriptions_Manager::prepare_renewal($subscription->id);
+ $order_id = $subscription->get_last_order();
+ $order = $this->find_order_by_id($order_id);
+ $subscription_id = $renew_infos['vindi_subscription_id'];
+ $order_post_meta = get_post_meta($order->id, 'vindi_order', true);
+
+ $order_post_meta[$subscription_id]['cycle'] = $renew_infos['cycle'];
+ $order_post_meta[$subscription_id]['product'] = $renew_infos['plan_name'];
+ $order_post_meta[$subscription_id]['bill'] = array(
+ 'id' => $renew_infos['bill_id'],
+ 'status' => $renew_infos['bill_status'],
+ 'bank_slip_url' => $renew_infos['bill_print_url'],
+ );
+ update_post_meta($order->id, 'vindi_order', $order_post_meta);
+
+ $this->vindi_settings->logger->log('Novo Período criado: Pedido #'.$order->id);
+
+ // We've already processed the renewal
+ remove_action( 'woocommerce_scheduled_subscription_payment', 'WC_Subscriptions_Manager::prepare_renewal' );
+ }
+
+ /**
+ * Process bill_created event from webhook
+ * @param $data array
+ */
+ private function bill_created($data)
+ {
+ if (empty($data->bill->subscription)) {
+ return;
+ }
+
+ $renew_infos = [
+ 'wc_subscription_id' => $data->bill->subscription->code,
+ 'vindi_subscription_id' => $data->bill->subscription->id,
+ 'plan_name' => str_replace('[WC] ', '', $data->bill->subscription->plan->name),
+ 'cycle' => $data->bill->period->cycle,
+ 'bill_status' => $data->bill->status,
+ 'bill_id' => $data->bill->id,
+ 'bill_print_url' => $data->bill->charges[0]->print_url
+ ];
+
+ if (!$this->subscription_has_order_in_cycle(
+ $renew_infos['vindi_subscription_id'],
+ $renew_infos['cycle']
+ )) {
+ $this->subscription_renew($renew_infos);
+ }
+ }
+
+ /**
+ * Process bill_paid event from webhook
+ * @param $data array
+ */
+ private function bill_paid($data)
+ {
+ if(empty($data->bill->subscription)) {
+ $order = $this->find_order_by_id($data->bill->code);
+
+ $vindi_order = get_post_meta($order->id, 'vindi_order', true);
+ if(is_array($vindi_order)) {
+ $vindi_order['single_payment']['bill']['status'] = $data->bill->status;
+ } else {
+ return;
+ }
+ } else {
+ $vindi_subscription_id = $data->bill->subscription->id;
+ $cycle = $data->bill->period->cycle;
+ $order = $this->find_order_by_subscription_and_cycle($vindi_subscription_id, $cycle);
+
+ $vindi_order = get_post_meta($order->id, 'vindi_order', true);
+ if(is_array($vindi_order)) {
+ $vindi_order[$vindi_subscription_id]['bill']['status'] = $data->bill->status;
+ } else {
+ return;
+ }
+ }
+ update_post_meta($order->id, 'vindi_order', $vindi_order);
+
+ $all_bills_paid = [];
+ foreach ($vindi_order as $item) {
+ if ($item['bill']['status'] == 'paid') {
+ array_push($all_bills_paid, true);
+ } else {
+ array_push($all_bills_paid, false);
+ }
+ }
+
+ if(!empty($all_bills_paid) && !in_array(false, $all_bills_paid)) {
+ $new_status = $this->vindi_settings->get_return_status();
+ $order->update_status($new_status, __('O Pagamento foi realizado com sucesso pela Vindi.',
+ VINDI));
+ $this->update_next_payment($data);
+ }
+ }
+
+ /**
+ * Process bill_canceled event from webhook
+ * @param $data array
+ */
+ private function bill_canceled($data)
+ {
+ if(empty($data->bill->subscription)) {
+ $order = $this->find_order_by_id($data->bill->code);
+ } else {
+ $vindi_subscription_id = $data->bill->subscription->id;
+ $cycle = $data->bill->period->cycle;
+ $order = $this->find_order_by_subscription_and_cycle($vindi_subscription_id, $cycle);
+ }
+
+ $order->update_status('cancelled', __('Pagamento cancelado dentro da Vindi!', VINDI));
+ }
+
+ /**
+ * Process issue_created event from webhook
+ * @param $data array
+ */
+ private function issue_created($data)
+ {
+ $issue_type = $data->issue->issue_type;
+ $issue_status = $data->issue->status;
+ $item_type = strtolower($data->issue->item_type);
+
+ if('charge_underpay' !== $issue_type)
+ throw new Exception(sprintf(__('Pendência criada com o tipo "%s" não processada!', VINDI), $issue_type));
+
+ if('open' !== $issue_status)
+ throw new Exception(sprintf(__('Pendência criada com o status "%s" não processada!', VINDI), $issue_status));
+
+ if('charge' !== $item_type)
+ throw new Exception(sprintf(__('Pendência criada com o item do tipo "%s" não processada!', VINDI), $item_type));
+
+ $item_id = (int) $data->issue->item_id;
+ $issue_data = $data->issue->data;
+ $bill = $this->find_bill_by_charge_id($item_id);
+ $order = $this->find_order_by_bill_id($bill->id);
+
+ $order->add_order_note(sprintf(
+ __('Divergencia de valores do Pedido #%s: Valor Esperado R$ %s, Valor Pago R$ %s.', VINDI),
+ $order->id,
+ $issue_data->expected_amount,
+ $issue_data->transaction_amount
+ ));
+ }
+
+ /**
+ * Process charge_rejected event from webhook
+ * @param $data array
+ */
+ private function charge_rejected($data)
+ {
+ $order = $this->find_order_by_bill_id($data->charge->bill->id);
+
+ if($order->get_status() == 'pending'){
+ $order->update_status('failed', __('Pagamento rejeitado!', VINDI));
+ }else{
+ throw new Exception(sprintf(
+ __('Erro ao trocar status da fatura para "failed" pois a fatura #%s não está mais pendente!', VINDI),
+ $data->charge->bill->id
+ ));
+ }
+ }
+
+ /**
+ * Process subscription_canceled event from webhook
+ * @param $data array
+ */
+ private function subscription_canceled($data)
+ {
+ $subscription = $this->find_subscription_by_id($data->subscription->code);
+
+ if ($this->vindi_settings->get_synchronism_status()
+ && ($subscription->has_status('cancelled')
+ || $subscription->has_status('pending-cancel')
+ || $subscription->has_status('on-hold'))) {
+ return;
+ }
+
+ if ($this->vindi_settings->dependencies->is_wc_memberships_active()) {
+ $subscription->update_status('pending-cancel');
+ return;
+ }
+ $subscription->update_status('cancelled');
+ }
+
+ /**
+ * Process subscription_reactivated event from webhook
+ * @param $data array
+ */
+ private function subscription_reactivated($data)
+ {
+ if ($this->vindi_settings->get_synchronism_status()){
+ $subscription_id = $data->subscription->code;
+ $subscription = $this->find_subscription_by_id($subscription_id);
+ $subscription->update_status('active', sprintf(__('Assinatura %s reativada pela Vindi.', VINDI), $subscription_id));
+ }
+ }
+
+ /**
+ * find a subscription by id
+ * @param int id
+ * @return WC_Subscription
+ */
+ private function find_subscription_by_id($id)
+ {
+ $subscription = wcs_get_subscription($id);
+
+ if(empty($subscription))
+ throw new Exception(sprintf(__('Assinatura #%s não encontrada!', VINDI), $id), 2);
+
+ return $subscription;
+ }
+
+ /**
+ * @param int id
+ *
+ * @return WC_Subscription
+ */
+ private function find_bill_by_charge_id($id)
+ {
+ $charge = $this->routes->getCharge($id);
+
+ if(empty($charge))
+ throw new Exception(sprintf(__('Cobrança #%s não encontrada!', VINDI), $id), 2);
+
+ return (object) $charge['bill'];
+ }
+
+ /**
+ * find a order by id
+ * @param int id
+ *
+ * @return WC_Order
+ */
+ private function find_order_by_id($id)
+ {
+ $order = wc_get_order($id);
+
+ if(empty($order))
+ throw new Exception(sprintf(__('Pedido #%s não encontrado!', VINDI), $id), 2);
+
+ return $order;
+ }
+
+ /**
+ * find orders by bill_id meta
+ *
+ * @param int $bill_id
+ *
+ * @return WC_Order
+ */
+ private function find_order_by_bill_id($bill_id)
+ {
+ $args = array(
+ 'post_type' => 'shop_order',
+ 'meta_key' => 'vindi_bill_id',
+ 'meta_value' => $bill_id,
+ 'post_status' => 'any',
+ );
+
+ $query = new WP_Query($args);
+
+ if(false === $query->have_posts())
+ throw new Exception(sprintf(__('Pedido com bill_id #%s não encontrado!', VINDI), $bill_id), 2);
+
+ return wc_get_order($query->post->ID);
+ }
+
+ /**
+ * Query orders containing cycle meta
+ *
+ * @param int $subscription_id
+ * @param int $cycle
+ *
+ * @return WC_Order
+ */
+ private function find_order_by_subscription_and_cycle($subscription_id, $cycle)
+ {
+ $query = $this->query_order_by_metas(array(
+ array(
+ 'key' => 'vindi_order',
+ 'value' => 'i:'.$subscription_id.';a:3:{s:5:"cycle";i:'.$cycle.';',
+ 'compare' => 'LIKE'
+ ),
+ ));
+
+ if(false === $query->have_posts())
+ throw new Exception(sprintf(__('Pedido da assinatura #%s para o ciclo #%s não encontrado!', VINDI), $subscriptionn_id, $cycle), 2);
+
+ return wc_get_order($query->post->ID);
+ }
+
+ /**
+ * @param int $subscription_id
+ * @param int $cycle
+ *
+ * @return boolean
+ */
+ private function subscription_has_order_in_cycle($subscription_id, $cycle)
+ {
+ $query = $this->query_order_by_metas(array(
+ array(
+ 'key' => 'vindi_order',
+ 'value' => 'i:'.$subscription_id.';a:3:{s:5:"cycle";i:'.$cycle.';',
+ 'compare' => 'LIKE'
+ ),
+ ));
+
+ return $query->have_posts();
+ }
+
+ /**
+ * @param array $metas
+ *
+ * @return WP_Query
+ */
+ private function query_order_by_metas(array $metas)
+ {
+ $args = array(
+ 'post_type' => 'shop_order',
+ 'meta_query' => $metas,
+ 'post_status' => 'any',
+ );
+
+ return new WP_Query($args);
+ }
+
+ /**
+ * Update next payment schedule of subscription
+ *
+ * @param $data object
+ */
+ private function update_next_payment($data)
+ {
+ // let's find the subscription in the API
+ // we need this step because the actual next billing date does not come from the /bill webhook
+ $vindi_subscription = $this->routes->getSubscription($data->bill->subscription->id);
+
+ if ($vindi_subscription && isset($vindi_subscription['next_billing_at'])) {
+
+ $next_billing_at = $vindi_subscription['next_billing_at'];
+
+ $end_at = $vindi_subscription['end_at'];
+
+ // na api, quando o plano é de cobrança única,
+ // o next_billing_at é 1 segundo maior que o end_at
+ // quando isso acontecer, o next_payment do wc deve ser null
+ // (a issue #134 tem mais informações do problema)
+
+ if ($next_billing_at > $end_at) {
+ return false;
+ }
+
+ // format next payment date
+ $next_payment = $this->format_date($next_billing_at);
+
+ // format end date
+ $end_date = $this->format_date($end_at);
+
+ // find our wc_subscription
+ $subscription = $this->find_subscription_by_id($data->bill->subscription->code);
+
+ // update the subscription dates
+ $subscription->update_dates(array('next_payment' => $next_payment));
+ $subscription->update_dates(array('end_date' => $end_date));
+ }
+ }
+
+ private function format_date($date)
+ {
+ return date('Y-m-d H:i:s', strtotime($date));
+ }
+}
\ No newline at end of file
diff --git a/src/templates/admin-gateway-settings.html.php b/src/templates/admin-gateway-settings.html.php
new file mode 100644
index 0000000..4c685e5
--- /dev/null
+++ b/src/templates/admin-gateway-settings.html.php
@@ -0,0 +1,16 @@
+
+
+vindi_settings->check_ssl()): ?>
+
+
+ :
+ Certificado SSL para ativar este método de pagamento em modo de produção. Por favor, verifique se um certificado SSL está instalado em seu servidor !')); ?>
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/templates/admin-settings.html.php b/src/templates/admin-settings.html.php
index e235401..77344e3 100644
--- a/src/templates/admin-settings.html.php
+++ b/src/templates/admin-settings.html.php
@@ -1,7 +1,54 @@
-
+
+
+check_ssl()): ?>
+
+
+ :
+ Certificado SSL para ativar este método de pagamento em modo de produção. Por favor, verifique se um certificado SSL está instalado em seu servidor!', VINDI)); ?>
+
+
+
-
+
+get_api_key();
+ if(!empty($api_key))
+ $merchant = get_transient('vindi_merchant');
+?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
%s', VINDI), $merchant['name']) ?>
+
+
+
%s', VINDI), ucwords($merchant['status'])) ?>
+
+
+
+
+
%s ', VINDI), $settings->api->last_error); ?>
+
+
+
+
diff --git a/src/templates/bankslip-checkout.html.php b/src/templates/bankslip-checkout.html.php
new file mode 100644
index 0000000..deaa71c
--- /dev/null
+++ b/src/templates/bankslip-checkout.html.php
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+ Modo Trial. Este modo é proposto para a realização de testes e, portanto, nenhum pedido será efetivamente cobrado.', VINDI); ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/templates/bankslip-download.html.php b/src/templates/bankslip-download.html.php
new file mode 100644
index 0000000..2650196
--- /dev/null
+++ b/src/templates/bankslip-download.html.php
@@ -0,0 +1,30 @@
+
+
+
+
diff --git a/src/templates/creditcard-checkout.html.php b/src/templates/creditcard-checkout.html.php
new file mode 100644
index 0000000..7602514
--- /dev/null
+++ b/src/templates/creditcard-checkout.html.php
@@ -0,0 +1,188 @@
+
+
+
+
+
+
+ Modo Trial. Este modo é proposto para a realização de testes e, portanto, nenhum pedido será efetivamente cobrado.', VINDI); ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0123 4567 8910 1112
+ JOÃO DA SILVA
+
+
+
+
+ 01/23
+ VALID
+ THRU
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 985
+
+
+
+
+
+ João da Silva
+
+
+
+
+
+
+
+
+
+
+
+ *
+
+
+ $price): ?>
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/utils/Conversions.php b/src/utils/Conversions.php
new file mode 100644
index 0000000..6357b41
--- /dev/null
+++ b/src/utils/Conversions.php
@@ -0,0 +1,72 @@
+ 1,
+ "month" => 30,
+ "week" => 7,
+ "year" => 365,
+ );
+
+ $verifyType = $types[$type];
+
+ if(!$verifyType) {
+ return false;
+ }
+
+ return intval($number) * intval($verifyType);
+
+ }
+ /**
+ * Converts the months, weeks and years of a Trial period into days.
+ *
+ * Used to send days in parameter to Vindi.
+ *
+ *
+ * @since 1.0.1
+ *
+ * @return number
+ */
+ public static function convert_interval($interval_count, $interval_type = 'month') {
+ $interval_multiplier = array(
+ "day" => 1,
+ "week" => 7,
+ "month" => 1,
+ "year" => 12,
+ );
+ $interval_types = array(
+ "day" => "days",
+ "week" => "days",
+ "month" => "months",
+ "year" => "months",
+ );
+
+ $get_interval_multiplier = $interval_multiplier[$interval_type];
+ $get_type = $interval_types[$interval_type];
+
+ if(!$get_type || !$get_interval_multiplier) {
+ return false;
+ }
+
+ return array(
+ 'interval' => $get_type,
+ 'interval_count' => intval($interval_count) * intval($get_interval_multiplier)
+ );
+
+ }
+}
+
+?>
diff --git a/src/utils/DefinitionVariables.php b/src/utils/DefinitionVariables.php
index 40c70f5..c9f88bf 100644
--- a/src/utils/DefinitionVariables.php
+++ b/src/utils/DefinitionVariables.php
@@ -5,17 +5,21 @@
define('VINDI_MININUM_WP_VERSION', '5.0');
define('VINDI_MININUM_PHP_VERSION', '5.6');
-define('VINDI', 'vindi-woocommerce');
+define('VINDI', 'vindi-payment-gateway');
-define('VINDI__FILE__', dirname(dirname(__FILE__)));
-define('VINDI_PLUGIN_BASE', plugin_basename(VINDI__FILE__));
-define('VINDI_PATH', plugin_dir_path(VINDI__FILE__));
+define('VINDI_FILE', dirname(dirname(__FILE__)));
+define('VINDI_PLUGIN_BASE', plugin_basename(VINDI_FILE));
+define('VINDI_PATH', plugin_dir_path(VINDI_FILE));
-define('VINDI_SRC', plugin_dir_path(VINDI__FILE__) . '/src/');
+define('VINDI_SRC', plugin_dir_path(VINDI_FILE) . '/src/');
if (defined('VINDI_TESTS') && VINDI_TESTS) {
define('VINDI_URL', 'file://' . VINDI_PATH);
} else {
- define('VINDI_URL', plugins_url('/', VINDI__FILE__));
+ define('VINDI_URL', plugins_url('/', VINDI_FILE));
}
+
+define('PREFIX_PRODUCT', '[WC] ');
+
+define('PREFIX_PLAN', '[WC] ');
diff --git a/src/utils/FrontendFilesLoader.php b/src/utils/FrontendFilesLoader.php
new file mode 100644
index 0000000..e124c8d
--- /dev/null
+++ b/src/utils/FrontendFilesLoader.php
@@ -0,0 +1,31 @@
+
+
+ 1 &&
+ $post_data['payment_method'] === 'vindi-credit-card'
+ ) {
+ global $woocommerce;
+ $interest_rate = get_option('woocommerce_vindi-credit-card_settings', true)['interest_rate'];
+ $installments = intval($post_data['vindi_cc_installments']);
+ $tax_total = 0;
+ $taxes = $cart->get_taxes();
+ foreach($taxes as $tax) $tax_total += $tax;
+ $cart_total = ($cart->get_cart_contents_total() + $cart->get_shipping_total() + $tax_total);
+ $total_price = $cart_total * (1 + (($interest_rate / 100) * ($installments - 1)));
+ $interest_price = (float) $total_price - $cart_total;
+ WC()->cart->add_fee(__('Juros', VINDI), $interest_price);
+ }
+ }
+}
diff --git a/src/utils/PaymentGateway.php b/src/utils/PaymentGateway.php
index 657a79c..00e7348 100644
--- a/src/utils/PaymentGateway.php
+++ b/src/utils/PaymentGateway.php
@@ -3,7 +3,7 @@
exit;
}
-include_once VINDI_PATH . 'src/helpers/VindiHelpers.php';
+include_once VINDI_PATH . 'src/services/VindiHelpers.php';
/**
* Abstract class that will be inherited by all payment methods.
@@ -15,6 +15,51 @@
abstract class VindiPaymentGateway extends WC_Payment_Gateway_CC
{
+ /**
+ * @var bool
+ */
+ protected $validated = true;
+
+ /**
+ * @var VindiSettings
+ */
+ public $vindi_settings;
+
+ /**
+ * @var VindiControllers
+ */
+ public $controllers;
+
+ /**
+ * @var VindiLogger
+ */
+ protected $logger;
+
+ /**
+ * @var VindiRoutes
+ */
+ protected $routes;
+
+ /**
+ * Should return payment type for payment processing.
+ * @return string
+ */
+ public abstract function type();
+
+ public function __construct(VindiSettings $vindi_settings, VindiControllers $controllers)
+ {
+ $this->vindi_settings = $vindi_settings;
+ $this->controllers = $controllers;
+ $this->logger = $this->vindi_settings->logger;
+ $this->routes = $vindi_settings->routes;
+ $this->title = $this->get_option('title');
+ $this->enabled = $this->get_option('enabled');
+
+ if (is_admin()) {
+ add_action('woocommerce_update_options_payment_gateways_' . $this->id, array(&$this, 'process_admin_options'));
+ }
+ }
+
/**
* Create the level 3 data array to send to Vindi when making a purchase.
*
@@ -86,4 +131,183 @@ public function is_valid_br_zip_code($zip)
{
return !empty($zip) && preg_match('/^[0-9]{5,5}([- ]?[0-9]{3,3})?$/', $zip);
}
+
+ /**
+ * Admin Panel Options
+ */
+ public function admin_options()
+ {
+ $this->vindi_settings->get_template('admin-gateway-settings.html.php', array('gateway' => $this));
+ }
+
+ /**
+ * Get the users country either from their order, or from their customer data
+ * @return string|null
+ */
+ public function get_country_code()
+ {
+ if (isset($_GET['order_id'])) {
+ $order = new WC_Order($_GET['order_id']);
+ return $order->billing_country;
+ } elseif ($this->vindi_settings->woocommerce->customer->get_billing_country()) {
+ return $this->vindi_settings->woocommerce->customer->get_billing_country();
+ }
+ }
+
+ /**
+ * Validate plugin settings
+ * @return bool
+ */
+ public function validate_settings()
+ {
+ $currency = get_option('woocommerce_currency');
+ $api_key = $this->vindi_settings->get_api_key();
+ return in_array($currency, ['BRL']) && ! empty($api_key);
+ }
+
+ /**
+ * Process the payment
+ *
+ * @param int $order_id
+ *
+ * @return array
+ */
+ public function process_payment($order_id)
+ {
+ $this->logger->log(sprintf('Processando pedido %s.', $order_id));
+ $order = wc_get_order($order_id);
+ $payment = new VindiPaymentProcessor($order, $this, $this->vindi_settings, $this->controllers);
+
+ // exit if validation by validate_fields() fails
+ if (! $this->validated) {
+ return false;
+ }
+
+ // Validate plugin settings
+ if (! $this->validate_settings()) {
+ return $payment->abort(__('O Pagamento foi cancelado devido a erro de configuração do meio de pagamento.', VINDI));
+ }
+
+ try {
+ $response = $payment->process();
+ $order->reduce_order_stock();
+ } catch (Exception $e) {
+ $response = array(
+ 'result' => 'fail',
+ 'redirect' => '',
+ );
+ }
+
+ return $response;
+ }
+
+ /**
+ * Check if the order is a Single Payment Order (not a Subscription).
+ * @return bool
+ */
+ protected function is_single_order()
+ {
+ $types = [];
+
+ foreach ($this->vindi_settings->woocommerce->cart->cart_contents as $item) {
+ $types[] = $item['data']->get_type();
+ }
+
+ return !(boolean) preg_grep('/subscription/', $types);
+ }
+
+ /**
+ * Process a refund.
+ *
+ * @param int $order_id Order ID.
+ * @param float $amount Refund amount.
+ * @param string $reason Refund reason.
+ * @return bool|WP_Error
+ */
+ public function process_refund($order_id, $amount = null, $reason = '') {
+ $order = wc_get_order($order_id);
+
+ if (!$this->can_refund_order($order)) {
+ return new WP_Error('error', __('Reembolso falhou.', VINDI));
+ }
+
+ if($amount < $order->get_total()) {
+ return new WP_Error('error', __('Não é possível realizar reembolsos parciais, faça um reembolso manual caso você opte por esta opção.', VINDI));
+ }
+
+ $results = [];
+ $order_meta = get_post_meta($order->id, 'vindi_order', true);
+ foreach ($order_meta as $key => $order_item) {
+ $bill_id = $order_item['bill']['id'];
+
+ $result = $this->refund_transaction($bill_id, null, $reason);
+
+ $this->logger->log('Resultado do reembolso: ' . wc_print_r($result, true));
+ switch (strtolower($result['status'])) {
+ case 'success':
+ $order->add_order_note(
+ /* translators: 1: Refund amount, 2: Refund ID */
+ sprintf(__('[Transação #%2$s]: reembolsado R$%1$s', VINDI), $result['amount'], $result['id'])
+ );
+ break;
+ }
+ if(isset($result->errors)) {
+ throw new Exception($result->errors[0]->message);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Get refund request args.
+ *
+ * @param WC_Order $order Order object.
+ * @param float $amount Refund amount.
+ * @param string $reason Refund reason.
+ * @return array
+ */
+ public function get_refund_request($bill_id, $amount = null, $reason = '') {
+ $request = array(
+ 'cancel_bill' => true,
+ 'comments' => strip_tags(wc_trim_string($reason, 255)),
+ );
+ if (!is_null($amount) ) {
+ $request['amount'] = $amount;
+ }
+ return apply_filters('vindi_refund_request', $request, $bill_id, $amount, $reason);
+ }
+
+ /**
+ * Refund an order via PayPal.
+ *
+ * @param WC_Order $order Order object.
+ * @param float $amount Refund amount.
+ * @param string $reason Refund reason.
+ * @return object Either an object of name value pairs for a success, or a WP_ERROR object.
+ */
+ public function refund_transaction($bill_id, $amount = null, $reason = '') {
+ $data = $this->get_refund_request($bill_id, $amount, $reason);
+ $last_charge = $this->find_bill_last_charge($bill_id);
+ $charge_id = $last_charge['id'];
+ $refund = $this->routes->refundCharge($charge_id, $data);
+
+ if (empty($refund)) {
+ throw new Exception(__('Resposta vazia', VINDI));
+ }
+
+ return $refund['last_transaction'];
+ }
+
+ private function find_bill_last_charge($bill_id)
+ {
+ $bill = $this->routes->findBillById($bill_id);
+
+ if(!$bill) {
+ throw new Exception(sprintf(__('A fatura com bill_id #%s não foi encontrada!', VINDI), $bill_id), 2);
+ }
+
+ $charges = $bill['charges'];
+ return end($charges);
+ }
};
diff --git a/src/utils/PaymentProcessor.php b/src/utils/PaymentProcessor.php
new file mode 100644
index 0000000..6bcbca6
--- /dev/null
+++ b/src/utils/PaymentProcessor.php
@@ -0,0 +1,1050 @@
+order = $order;
+ $this->gateway = $gateway;
+ $this->vindi_settings = $vindi_settings;
+ $this->logger = $vindi_settings->logger;
+ $this->routes = $vindi_settings->routes;
+ $this->controllers = $controllers;
+ $this->shipping_added = false;
+ }
+
+ /**
+ * Check if the order contains any subscription.
+ *
+ * @return int Order type
+ */
+ public function get_order_type()
+ {
+ if (function_exists('wcs_order_contains_subscription') && wcs_order_contains_subscription($this->order, array('any'))) {
+ return static::ORDER_TYPE_SUBSCRIPTION;
+ }
+
+ return static::ORDER_TYPE_SINGLE;
+ }
+
+
+ /**
+ * Find or create a customer within Vindi using the given credentials.
+ *
+ * @return array Vindi customer array
+ */
+ public function get_customer()
+ {
+ $current_user = wp_get_current_user();
+ $vindi_customer_id = get_user_meta($current_user->ID, 'vindi_customer_id', true);
+ $vindi_customer = $this->routes->findCustomerById($vindi_customer_id);
+ if(!$vindi_customer) {
+ $vindi_customer = $this->controllers->customers->create($current_user->ID, $this->order);
+ }
+
+
+ // if($this->vindi_settings->send_nfe_information()) {
+ $vindi_customer = $this->controllers->customers->update($current_user->ID, $this->order);
+ // }
+
+ if ($this->is_cc())
+ $this->create_payment_profile($vindi_customer['id']);
+
+ $this->logger->log(sprintf('Cliente Vindi: %s', $vindi_customer['id']));
+
+ return $vindi_customer;
+ }
+
+ /**
+ * Build the credit card payment type.
+ *
+ * @param int $customer_id Vindi customer id
+ *
+ * @return array
+ */
+ public function get_cc_payment_type($customer_id)
+ {
+ if ($this->gateway->verify_user_payment_profile()) return false;
+
+ return array(
+ 'customer_id' => $customer_id,
+ 'holder_name' => $_POST['vindi_cc_fullname'],
+ 'card_expiration' => $_POST['vindi_cc_monthexpiry'] . '/' . $_POST['vindi_cc_yearexpiry'],
+ 'card_number' => $_POST['vindi_cc_number'],
+ 'card_cvv' => $_POST['vindi_cc_cvc'],
+ 'payment_method_code' => $this->payment_method_code() ,
+ 'payment_company_code' => $_POST['vindi_cc_paymentcompany'],
+ );
+ }
+
+ /**
+ * Check if payment method is "Credit Card"
+ *
+ * @return bool
+ */
+ public function is_cc()
+ {
+ return 'cc' === $this->gateway->type();
+ }
+
+ /**
+ * Check if payment method is "Bank Slip"
+ *
+ * @return bool
+ */
+ public function is_bank_slip()
+ {
+ return 'bank_slip' === $this->gateway->type();
+ }
+
+ /**
+ * Retrieve payment method code
+ *
+ * @return string Vindi payment method code
+ */
+ public function payment_method_code()
+ {
+ return $this->is_cc() ? 'credit_card' : 'bank_slip';
+ }
+
+ /**
+ * Interrupt the payment process and throw an error if needed.
+ * Log the message, add it to the order note and send an alert to the user
+ *
+ * @param string $message The error message
+ * @param bool $throw_exception When true an exception is thrown
+ *
+ * @return bool Always returns false
+ *
+ * @throws Exception
+ */
+ public function abort($message, $throw_exception = false)
+ {
+ $this->logger->log($message);
+ $this->order->add_order_note($message);
+ wc_add_notice($message, 'error');
+ if ($throw_exception) throw new Exception($message);
+
+ return false;
+ }
+
+ /**
+ * Check if the order type is valid and process it.
+ *
+ * @return array|void
+ *
+ * @throws Exception
+ */
+ public function process()
+ {
+ switch ($orderType = $this->get_order_type()) {
+ case static ::ORDER_TYPE_SINGLE:
+ case static ::ORDER_TYPE_SUBSCRIPTION:
+ return $this->process_order();
+ case static ::ORDER_TYPE_INVALID:
+ default:
+ return $this->abort(__('Falha ao processar carrinho de compras. Verifique os itens escolhidos e tente novamente.', VINDI) , true);
+ }
+ }
+
+ /**
+ * Process current order.
+ *
+ * @return array
+ *
+ * @throws Exception
+ */
+ public function process_order()
+ {
+ if($this->order_has_trial_and_simple_product()) {
+ $message = __('Não é possível comprar produtos simples e assinaturas com trial no mesmo pedido!', VINDI);
+ $this->order->update_status('failed', $message);
+ wc_add_notice($message, 'error');
+
+ throw new Exception($message);
+ return false;
+ }
+
+ $customer = $this->get_customer();
+ $order_items = $this->order->get_items();
+ $bills = [];
+ $order_post_meta = [];
+ $bill_products = [];
+ $subscriptions_ids = [];
+ foreach ($order_items as $order_item) {
+ $product = $order_item->get_product();
+
+ if($this->is_subscription_type($product)) {
+ $subscription = $this->create_subscription($customer['id'], $order_item);
+ $subscription_order_post_meta = [];
+ $subscription_id = $subscription['id'];
+ array_push($subscriptions_ids, $subscription_id);
+ $wc_subscription_id = $subscription['wc_id'];
+ $subscription_bill = $subscription['bill'];
+ $order_post_meta[$subscription_id]['cycle'] = $subscription['current_period']['cycle'];
+ $order_post_meta[$subscription_id]['product'] = $product->name;
+ $order_post_meta[$subscription_id]['bill'] = $this->create_bill_meta_for_order($subscription_bill);
+
+ $subscription_order_post_meta[$subscription_id]['cycle'] = $subscription['current_period']['cycle'];
+ $subscription_order_post_meta[$subscription_id]['product'] = $product->name;
+ $subscription_order_post_meta[$subscription_id]['bill'] = $this->create_bill_meta_for_order($subscription_bill);
+ $bills[] = $subscription['bill'];
+ if ($message = $this->cancel_if_denied_bill_status($subscription['bill'])) {
+ $wc_subscription = wcs_get_subscription($wc_subscription_id);
+ $wc_subscription->update_status('cancelled', __($message, VINDI));
+ $this->order->update_status('cancelled', __($message, VINDI));
+ $this->suspend_subscriptions($subscriptions_ids);
+ $this->cancel_bills($bills, __('Algum pagamento do pedido não pode ser processado', VINDI));
+ $this->abort(__($message, VINDI) , true);
+ }
+
+ update_post_meta($wc_subscription_id, 'vindi_subscription_id', $subscription_id);
+ update_post_meta($wc_subscription_id, 'vindi_order', $subscription_order_post_meta);
+ continue;
+ }
+
+ $bill_products[] = $order_item;
+ }
+
+ if(!empty($bill_products)) {
+ $single_payment_bill = $this->create_bill($customer['id'], $bill_products);
+ $order_post_meta['single_payment']['product'] = 'Produtos Avulsos';
+ $order_post_meta['single_payment']['bill'] = $this->create_bill_meta_for_order($single_payment_bill);
+ $bills[] = $single_payment_bill;
+ if ($message = $this->cancel_if_denied_bill_status($single_payment_bill)) {
+ $this->order->update_status('cancelled', __($message, VINDI));
+ $this->suspend_subscriptions($subscriptions_ids);
+ $this->cancel_bills($bills, __('Algum pagamento do pedido não pode ser processado', VINDI));
+ $this->abort(__($message, VINDI) , true);
+ }
+ }
+
+ update_post_meta($this->order->id, 'vindi_order', $order_post_meta);
+
+ WC()->session->__unset('current_payment_profile');
+ WC()->session->__unset('current_customer');
+
+ remove_action('woocommerce_scheduled_subscription_payment', 'WC_Subscriptions_Manager::prepare_renewal');
+
+ return $this->finish_payment($bills);
+ }
+
+
+
+ /**
+ * Create a payment profile for the customer
+ *
+ * @param int $customer_id Vindi customer id
+ *
+ * @throws Exception
+ */
+ protected function create_payment_profile($customer_id)
+ {
+ $cc_info = $this->get_cc_payment_type($customer_id);
+
+ if (false === $cc_info)
+ return;
+
+ $payment_profile = $this->routes->createCustomerPaymentProfile($cc_info);
+
+ if (!$payment_profile)
+ $this->abort(__('Falha ao registrar o método de pagamento. Verifique os dados e tente novamente.', VINDI) , true);
+
+ if ($this->gateway->verify_method())
+ $this->verify_payment_profile($payment_profile['id']);
+ }
+
+ /**
+ * Check if the payment profile is valid
+ *
+ * @param int $payment_profile_id The customer's payment profile id
+ *
+ * @throws Exception
+ */
+ protected function verify_payment_profile($payment_profile_id)
+ {
+ if (!$this->routes->verifyCustomerPaymentProfile($payment_profile_id))
+ $this->abort(__('Não foi possível realizar a verificação do seu cartão de crédito!', VINDI) , true);
+ }
+
+ /**
+ * Get the subscription/product expiration time in months
+ *
+ * @param WC_Order_Item_Product $item
+ *
+ * @return int
+ */
+ private function get_cycle_from_product_type($item)
+ {
+ $product = method_exists($item, 'get_product') ? $item->get_product() : false;
+ if ($item['type'] == 'shipping' || $item['type'] == 'tax') {
+ if ($this->vindi_settings->get_shipping_and_tax_config()) return 1;
+ }
+ elseif (!$this->is_subscription_type($product) || $this->is_one_time_shipping($product)) {
+ return 1;
+ }
+ $cycles = get_post_meta($product->get_id(), '_subscription_length', true);
+ return $cycles > 0 ? $cycles : null;
+ }
+
+ /**
+ * Check if the product needs to be shipped only once
+ *
+ * @param WC_Product $product Woocommerce product
+ *
+ * @return bool
+ */
+ private function is_one_time_shipping($product)
+ {
+ return get_post_meta($product->get_id(), '_subscription_one_time_shipping', true) == 'yes';
+ }
+
+ /**
+ * Build the array of product(s), shipping, tax and discounts to send to Vindi
+ *
+ * @param string $order_type Order type. Possible values 'bill' and 'subscription', defaults to 'bill'
+ * @param WC_Order_Item_Product|WC_Order_Item_Product[] $product The product to be built. If the order type is 'bill' this will be an array of WC_Order_Item_Product,
+ * if it's 'subscription' it will be a single WC_Order_Item_Product.
+ *
+ * @return array
+ *
+ * @throws Exception
+ */
+ public function build_product_items($order_type = 'bill', $product)
+ {
+ $call_build_items = "build_product_items_for_{$order_type}";
+
+ if (false === method_exists($this, $call_build_items)) {
+ $this->abort(__("Ocorreu um erro ao gerar o seu pedido!", VINDI) , true);
+ }
+
+ $product_items = [];
+ $order_items = [];
+ if('bill' === $order_type) {
+ $order_items = $this->build_product_from_order_item($order_type, $product);
+ } else {
+ $order_items[] = $this->build_product_from_order_item($order_type, $product);
+ }
+ // TODO Buscar separadamente o valor de entrega
+ $order_items[] = $this->build_shipping_item($order_items);
+ $order_items[] = $this->build_tax_item($order_items);
+
+ if ('bill' === $order_type) {
+ $order_items[] = $this->build_discount_item_for_bill($order_items);
+ $order_items[] = $this->build_interest_rate_item($order_items);
+ }
+
+ foreach ($order_items as $order_item) {
+ if (empty($order_item)) {
+ continue;
+ }
+ $product_items[] = $this->$call_build_items($order_item);
+ }
+
+ if (empty($product_items)) {
+ return $this->abort(__('Falha ao recuperar informações sobre o produto na Vindi. Verifique os dados e tente novamente.', VINDI) , true);
+ }
+
+ return $product_items;
+ }
+
+ /**
+ * Retrives the product(s) information. Adds the vindi_id, the type and the price to it.
+ *
+ * @param string $order_type ('subscription' or 'bill')
+ * @param WC_Order_Item_Product|WC_Order_Item_Product[] $order_items. Subscriptions will pass only one order_item and
+ * Bills will pass an array with all the products to be processed
+ *
+ * @return array
+ */
+ protected function build_product_from_order_item($order_type, $order_items)
+ {
+ if('bill' === $order_type) {
+ foreach ($order_items as $key => $order_item) {
+ $product = $this->get_product($order_item);
+ $order_items[$key]['type'] = 'product';
+ $order_items[$key]['vindi_id'] = $product->vindi_id;
+ $order_items[$key]['price'] = (float)$order_items[$key]['subtotal'] / $order_items[$key]['qty'];
+ }
+ return $order_items;
+ }
+ $product = $this->get_product($order_items);
+ $order_items['type'] = 'product';
+ $order_items['vindi_id'] = $product->vindi_id;
+ if ($this->subscription_has_trial($product)) {
+ $matching_item = $this->get_trial_matching_subscription_item($order_items);
+ $order_items['price'] = (float)$matching_item['subtotal'] / $matching_item['qty'];
+ } else {
+ $order_items['price'] = (float)$order_items['subtotal'] / $order_items['qty'];
+ }
+
+ return $order_items;
+ }
+
+ /**
+ * Create the shipping item to be added to the bill.
+ *
+ * @param WC_Order_Item_Product[] $order_items. Array with all items to add
+ * the respective delivered value // TODO.
+ *
+ * @return array
+ */
+ protected function build_interest_rate_item($order_items)
+ {
+ $interest_rate_item = [];
+
+ if (!($this->is_cc() && $this->installments() > 1 && $this->gateway->is_interest_rate_enabled())) {
+ return $interest_rate_item;
+ }
+
+ $interest_rate = $this->gateway->get_interest_rate();
+ $installments = $this->installments();
+ $cart = WC()->cart;
+ $cart_total = $cart->total;
+ foreach ($cart->get_fees() as $index => $fee) {
+ if($fee->name == __('Juros', VINDI)) {
+ $cart_total -= $fee->amount;
+ }
+ }
+ $total_price = $cart_total * (1 + (($interest_rate / 100) * ($installments - 1)));
+ $interest_price = (float) $total_price - $cart_total;
+
+ $item = $this->routes->findOrCreateProduct("Juros", 'wc-interest-rate');
+ $interest_rate_item = array(
+ 'type' => 'interest_rate',
+ 'vindi_id' => $item['id'],
+ 'price' => $interest_price,
+ 'qty' => 1,
+ );
+ return $interest_rate_item;
+ }
+
+ /**
+ * Create the shipping item to be added to the bill.
+ *
+ * @param WC_Order_Item_Product[] $order_items. Array with all items to add
+ * the respective delivered value // TODO.
+ *
+ * @return array
+ */
+ protected function build_shipping_item($order_items)
+ {
+ $shipping_item = [];
+ $shipping_method = $this->order->get_shipping_method();
+
+ if (empty($shipping_method)) return $shipping_item;
+
+ foreach ($order_items as $order_item) {
+ $product = $order_item->get_product();
+
+ if($product->needs_shipping() && !$this->shipping_added) {
+ $item = $this->routes->findOrCreateProduct("Frete ($shipping_method)", sanitize_title($shipping_method));
+ $shipping_item = array(
+ 'type' => 'shipping',
+ 'vindi_id' => $item['id'],
+ 'price' => (float)$this->order->get_total_shipping(),
+ 'qty' => 1,
+ );
+ $this->shipping_added = true;
+
+ return $shipping_item;
+ }
+ }
+ }
+
+ /**
+ * Create the tax item.
+ *
+ * @param WC_Order_Item_Product[] $order_items. Products to calculate the tax amount
+ *
+ * @return array
+ */
+ protected function build_tax_item($order_items)
+ {
+ $taxItem = [];
+ $total_order_tax = $this->vindi_settings->woocommerce->cart->get_total_tax();
+ $total_tax = 0;
+ if (empty($total_order_tax)) {
+ return $taxItem;
+ }
+
+ foreach ($order_items as $order_item) {
+ if(!empty($order_item['type'])) {
+ if ($order_item['type'] === 'shipping') {
+ $total_tax += (float)($this->order->get_shipping_tax());
+ } else {
+ $total_tax += (float)($order_item->get_total_tax());
+ }
+ }
+ }
+
+ $item = $this->routes->findOrCreateProduct("Taxa", 'wc-tax');
+ $taxItem = array(
+ 'type' => 'tax',
+ 'vindi_id' => $item['id'],
+ 'price' => (float)$total_tax,
+ 'qty' => 1
+ );
+
+ return $taxItem;
+ }
+
+ /**
+ * Create discount item for a bill.
+ *
+ * @param WC_Order_Item_Product[] $order_items. All the products to calculate
+ * the discount amount
+ *
+ * @return array
+ */
+ protected function build_discount_item_for_bill($order_items)
+ {
+ $discount_item = [];
+ $coupons = array_values($this->vindi_settings->woocommerce->cart->get_coupons());
+ $bill_total_discount = 0;
+ foreach ($order_items as $order_item) {
+ if(isset($order_item['subtotal']) && isset($order_item['total'])) {
+ $bill_total_discount += (float) ($order_item['subtotal'] - $order_item['total']);
+ }
+ }
+
+ if (empty($bill_total_discount)) {
+ return $discount_item;
+ }
+
+ $item = $this->routes->findOrCreateProduct("Cupom de desconto", 'wc-discount');
+ $discount_item = array(
+ 'type' => 'discount',
+ 'vindi_id' => $item['id'],
+ 'price' => (float)$bill_total_discount * -1,
+ 'qty' => 1
+ );
+
+ return $discount_item;
+ }
+
+ /**
+ * Create bill product item to send to the Vindi API.
+ *
+ * @param WC_Order_Item_Product $order_item. The product to be converted to
+ * the correct product format.
+ *
+ * @return array
+ */
+ protected function build_product_items_for_bill($order_item)
+ {
+ $item = array(
+ 'product_id' => $order_item['vindi_id'],
+ 'quantity' => $order_item['qty'],
+ 'pricing_schema' => array(
+ 'price' => $order_item['price'],
+ 'schema_type' => 'per_unit'
+ )
+ );
+
+ if (
+ 'discount' == $order_item['type'] || 'shipping' == $order_item['type'] ||
+ 'tax' == $order_item['type'] || 'interest_rate' == $order_item['type']
+ ) {
+ $item = array(
+ 'product_id' => $order_item['vindi_id'],
+ 'amount' => $order_item['price']
+ );
+ }
+
+ return $item;
+ }
+
+ /**
+ * Create subscription product item to send to the Vindi API.
+ *
+ * @param WC_Order_Item_Product $order_item. The product to be converted to
+ * the correct product format.
+ *
+ * @return array
+ */
+ protected function build_product_items_for_subscription($order_item)
+ {
+ $plan_cycles = $this->get_cycle_from_product_type($order_item);
+ $product_item = array(
+ 'product_id' => $order_item['vindi_id'],
+ 'quantity' => $order_item['qty'],
+ 'cycles' => $plan_cycles,
+ 'pricing_schema' => array(
+ 'price' => $order_item['price'],
+ 'schema_type' => 'per_unit'
+ )
+ );
+
+ if (!empty($this->order->get_total_discount()) && $order_item['type'] == 'line_item') {
+ $product_item['discounts'] = [];
+
+ $coupons = array_values($this->vindi_settings->woocommerce->cart->get_coupons());
+ foreach ($coupons as $coupon) {
+ if($this->coupon_supports_product($order_item, $coupon)) {
+ $product_item['discounts'][] = $this->build_discount_item_for_subscription($coupon, $plan_cycles);
+ }
+ }
+ }
+ return $product_item;
+ }
+
+ /**
+ * Verify that the coupon can be applied to the current product.
+ *
+ * @param WC_Order_Item_Product $order_item. The product.
+ * @param WC_Coupon $coupon. The coupon.
+ *
+ * @return bool
+ */
+ protected function coupon_supports_product($order_item, $coupon)
+ {
+ $product_id = $order_item->get_product()->id;
+ $included_products = $coupon->get_product_ids();
+ $excluded_products = $coupon->get_excluded_product_ids();
+
+ if(!empty($excluded_products)) {
+ if(in_array($product_id, $excluded_products)) {
+ // The coupon doesn't support the current product
+ return false;
+ }
+ }
+ if(!empty($included_products)) {
+ if(!in_array($product_id, $included_products)) {
+ // The coupon doesn't support the current product
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Create a discount item for a subscription.
+ *
+ * @param WC_Coupon $coupon. The coupon to be added.
+ * @param int $plan_cycles. The amount of cycles that the subscription has.
+ *
+ * @return array
+ */
+ protected function build_discount_item_for_subscription($coupon, $plan_cycles = 0) {
+ $discount_item = [];
+
+ $amount = $coupon->get_amount();
+ $discount_type = $coupon->get_discount_type();
+ if($discount_type == 'fixed_cart') {
+ $discount_item['discount_type'] = 'amount';
+ $discount_item['amount'] = $amount / $this->order->get_item_count();
+ $discount_item['cycles'] = 1;
+ return $discount_item;
+ } elseif(strpos($discount_type, 'fixed') !== false) {
+ $discount_item['discount_type'] = 'amount';
+ $discount_item['amount'] = $amount;
+ } elseif(strpos($discount_type, 'percent') !== false) {
+ $discount_item['discount_type'] = 'percentage';
+ $discount_item['percentage'] = $amount;
+ }
+ $discount_item['cycles'] = $this->config_discount_cycles($coupon, $plan_cycles);
+
+ return $discount_item;
+ }
+
+ /**
+ * Configure the discount cycles that the coupon will be used.
+ *
+ * @param WC_Coupon $coupon. The coupon to be added.
+ * @param int $plan_cycles. The amount of cycles that the subscription has.
+ *
+ * @return int
+ */
+ protected function config_discount_cycles($coupon, $plan_cycles = 0)
+ {
+ $get_plan_length =
+ function ($cycle_count, $plan_cycles)
+ {
+ if (!$cycle_count) {
+ return null;
+ }
+
+ if ($plan_cycles) {
+ return min($plan_cycles, $cycle_count);
+ }
+ return $cycle_count;
+ };
+ $cycle_count = get_post_meta($coupon->id, 'cycle_count', true);
+
+ switch ($cycle_count) {
+ case '0':
+ return null;
+ default:
+ return $get_plan_length($cycle_count, $plan_cycles);
+ }
+ }
+
+ /**
+ * Retrieve number of installments from order.
+ * If the order contains subscriptions the return will be 1,
+ * else it will be the amount selected by the user during checkout.
+ *
+ * @return int
+ */
+ protected function installments()
+ {
+ if ('credit_card' == $this->payment_method_code() && isset($_POST['vindi_cc_installments'])) return $_POST['vindi_cc_installments'];
+
+ return 1;
+ }
+
+ /**
+ * Retrieve Plan for Vindi Subscription.
+ *
+ * @param WC_Order_Item_Product $order_item
+ *
+ * @return int|bool
+ */
+ public function get_plan_from_order_item($order_item)
+ {
+ $product = $order_item->get_product();
+
+ if (isset($order_item['variation_id']) && $order_item['variation_id'] != 0) {
+ $vindi_plan = get_post_meta($order_item['variation_id'], 'vindi_plan_id', true);
+ if (empty($vindi_plan) || !is_numeric($vindi_plan) || is_null($vindi_plan) || $vindi_plan == 0) {
+ $vindi_plan = get_post_meta($product->get_id(), 'vindi_plan_id', true);
+ }
+ }
+ else $vindi_plan = get_post_meta($product->get_id(), 'vindi_plan_id', true);
+
+ if ($this->is_subscription_type($product) and !empty($vindi_plan)) return $vindi_plan;
+
+ $this->abort(__('O produto selecionado não é uma assinatura.', VINDI) , true);
+ }
+
+
+ /**
+ * Create a subscription within Vindi
+ *
+ * @param int $customer_id ID of the customer that placed the order
+ * @param WC_Order_item_product $order_item Item to add to the subscription.
+ *
+ * @return array
+ * @throws Exception
+ */
+ protected function create_subscription($customer_id, $order_item)
+ {
+ $vindi_plan = $this->get_plan_from_order_item($order_item);
+ $wc_subscription_id = VindiHelpers::get_matching_subscription($this->order, $order_item)->id;
+ $data = array(
+ 'customer_id' => $customer_id,
+ 'payment_method_code' => $this->payment_method_code(),
+ 'plan_id' => $vindi_plan,
+ 'product_items' => $this->build_product_items('subscription', $order_item),
+ 'code' => $wc_subscription_id,
+ 'installments' => $this->installments()
+ );
+
+ $subscription = $this->routes->createSubscription($data);
+
+ // TODO caso ocorra o erro no pagamento de uma assinatura cancelar as outras
+ if (!isset($subscription['id']) || empty($subscription['id'])) {
+ $this->logger->log(sprintf('Erro no pagamento item %s do pedido %s.', $order_item->name, $this->order->id));
+
+ $message = sprintf(__('Pagamento Falhou. (%s)', VINDI) , $this->vindi_settings->api->last_error);
+ $this->order->update_status('failed', $message);
+
+ throw new Exception($message);
+ }
+
+ $subscription['wc_id'] = $wc_subscription_id;
+
+ return $subscription;
+ }
+
+ /**
+ * Create a bill within Vindi
+ *
+ * @param int $customer_id ID of the customer that placed the order
+ * @param WC_Order_item_product[] $order_items Array with items to add to the bill.
+ *
+ * @return int
+ * @throws Exception
+ */
+ protected function create_bill($customer_id, $order_items)
+ {
+ $data = array(
+ 'customer_id' => $customer_id,
+ 'payment_method_code' => $this->payment_method_code() ,
+ 'bill_items' => $this->build_product_items('bill', $order_items),
+ 'code' => $this->order->id,
+ 'installments' => $this->installments()
+ );
+
+ $bill = $this->routes->createBill($data);
+
+ if (!$bill) {
+ $this->logger->log(sprintf('Erro no pagamento do pedido %s.', $this->order->id));
+ $message = sprintf(__('Pagamento Falhou. (%s)', VINDI) , $this->vindi_settings->api->last_error);
+ $this->order->update_status('failed', $message);
+
+ throw new Exception($message);
+ }
+ return $bill;
+ }
+
+ /**
+ * Create bill meta array to add to the order
+ *
+ * @param array $bill The bill returned from Vindi API
+ *
+ * @return array
+ */
+ protected function create_bill_meta_for_order($bill)
+ {
+ $bill_meta['id'] = $bill['id'];
+ $bill_meta['status'] = $bill['status'];
+ if (isset($bill['charges']) && count($bill['charges'])) {
+ $bill_meta['bank_slip_url'] = $bill['charges'][0]['print_url'];
+ }
+ return $bill_meta;
+ }
+
+ /**
+ * Check if bill was rejected by Vindi
+ *
+ * @param array $bill The bill returned from Vindi API
+ *
+ * @return bool|string
+ */
+ protected function cancel_if_denied_bill_status($bill)
+ {
+ if (empty($bill['charges'])) {
+ return false;
+ }
+
+ $last_charge = end($bill['charges']);
+ $transaction_status = $last_charge['last_transaction']['status'];
+ $denied_status = ['rejected' => 'Infelizmente não foi possível autorizar seu pagamento.', 'failure' => 'Ocorreu um erro ao aprovar a transação, tente novamente.'];
+
+ if (array_key_exists($transaction_status, $denied_status)) {
+ return $denied_status[$transaction_status];
+ }
+
+ return false;
+ }
+
+ /**
+ * Suspend subscription within Vindi
+ *
+ * @param array $subscriptions_ids Array with the IDs of subscriptions that were processed
+ */
+ protected function suspend_subscriptions($subscriptions_ids)
+ {
+ foreach ($subscriptions_ids as $subscription_id) {
+ $this->routes->suspendSubscription($subscription_id, true);
+ }
+ }
+
+ /**
+ * Suspend bills within Vindi
+ *
+ * @param array $bills Array with the bills that were processed
+ */
+ protected function cancel_bills($bills, $comments = '')
+ {
+ foreach ($bills as $bill) {
+ $this->routes->deleteBill($bill['id'], $comments);
+ }
+ }
+
+ /**
+ * Finish the payment
+ *
+ * @param array $bills Order bills returned from Vindi API
+ *
+ * @return array
+ */
+ protected function finish_payment($bills)
+ {
+ $this->vindi_settings->woocommerce->cart->empty_cart();
+
+ $bills_status = [];
+ foreach ($bills as $bill) {
+ if ($bill['status'] == 'paid') {
+ $data_to_log = sprintf('O Pagamento da fatura %s do pedido %s foi realizado com sucesso pela Vindi.', $bill['id'], $this->order->id);
+ $status_message = __('O Pagamento foi realizado com sucesso pela Vindi.', VINDI);
+ } else {
+ $data_to_log = sprintf('Aguardando pagamento da fatura %s do pedido %s pela Vindi.', $bill['id'], $this->order->id);
+ $status_message = __('Aguardando pagamento do pedido.', VINDI);
+ }
+ array_push($bills_status, $bill['status']);
+ $this->logger->log($data_to_log);
+ }
+ if(sizeof($bills_status) == sizeof(array_keys($bills_status, 'paid'))) {
+ $status = $this->vindi_settings->get_return_status();
+ } else {
+ $status = 'pending';
+ }
+ $this->order->update_status($status, $status_message);
+
+ return array(
+ 'result' => 'success',
+ 'redirect' => $this->order->get_checkout_order_received_url() ,
+ );
+ }
+
+ /**
+ * Find or create the product within Vindi
+ * and add the vindi id to the product array
+ *
+ * @param WC_Order_Item_Product $order_item
+ *
+ * @return WC_Product Woocommerce product array with a vindi id
+ */
+ protected function get_product($order_item)
+ {
+ $product = $order_item->get_product();
+ $product_id = $product->get_id();
+ $vindi_product_id = get_post_meta($product_id, 'vindi_product_id', true);
+
+ if (!$vindi_product_id) {
+ $vindi_product = null;
+ if(!$this->is_subscription_type($product)) {
+ $vindi_product = $this->controllers->products->create($product_id, '', '', true);
+ } else {
+ $vindi_product = $this->controllers->plans->create($product_id, '', '', true);
+ }
+
+ $vindi_product_id = $vindi_product['id'];
+ }
+
+ $product->vindi_id = (int) $vindi_product_id;
+ return $product;
+ }
+
+ /**
+ * Check if the order has a subscription with trial and simple products.
+ *
+ * @since 1.0.0
+ * @return bool
+ */
+ public function order_has_trial_and_simple_product()
+ {
+ $has_trial = false;
+ $has_simple_product = false;
+ $order_items = $this->order->get_items();
+ foreach ($order_items as $order_item) {
+ $product = $order_item->get_product();
+ if ($this->subscription_has_trial($product)) {
+ $has_trial = true;
+ if ($has_simple_product) return true;
+ } else {
+ $has_simple_product = true;
+ if ($has_trial) return true;
+ }
+ }
+ return $has_trial && $has_simple_product;
+ }
+
+ /**
+ * Check if the product is variable
+ *
+ * @param WC_Product $product
+ * @return bool
+ */
+ protected function is_variable(WC_Product $product)
+ {
+ return (boolean)preg_match('/variation/', $product->get_type());
+ }
+
+ /**
+ * Check if the product is a subscription
+ *
+ * @param WC_Product $product
+ * @return bool
+ */
+ protected function is_subscription_type(WC_Product $product)
+ {
+ return (boolean)preg_match('/subscription/', $product->get_type());
+ }
+
+ /**
+ * Check if the subscription has a trial period
+ *
+ * @param WC_Product $product
+ * @return bool
+ */
+ protected function subscription_has_trial(WC_Product $product)
+ {
+ return $this->is_subscription_type($product) && class_exists( 'WC_Subscriptions_Product' ) && WC_Subscriptions_Product::get_trial_length($product->get_id()) > 0;
+ }
+
+ /**
+ * Get trial item quantity, subtotal and total price.
+ *
+ * @param WC_Order_Item_Product $order_item
+ * @return WC_Order_Item_Product
+ */
+ protected function get_trial_matching_subscription_item(WC_Order_Item_Product $order_item)
+ {
+ $subscription = VindiHelpers::get_matching_subscription($this->order, $order_item);
+ $matching_item = VindiHelpers::get_matching_subscription_item($subscription, $order_item);
+ return $matching_item;
+ }
+}
diff --git a/src/utils/RedirectCheckout.php b/src/utils/RedirectCheckout.php
new file mode 100644
index 0000000..8f06b72
--- /dev/null
+++ b/src/utils/RedirectCheckout.php
@@ -0,0 +1,39 @@
+
+
+
diff --git a/src/utils/SubscriptionStatusHandler.php b/src/utils/SubscriptionStatusHandler.php
new file mode 100644
index 0000000..e71f3b4
--- /dev/null
+++ b/src/utils/SubscriptionStatusHandler.php
@@ -0,0 +1,158 @@
+vindi_settings = $vindi_settings;
+ $this->routes = $vindi_settings->routes;
+
+ add_action('woocommerce_subscription_status_cancelled',array(
+ &$this, 'cancelled_status'
+ ));
+
+ add_action('woocommerce_subscription_status_updated',array(
+ &$this, 'filter_pre_status'
+ ), 1, 3);
+
+ add_action('woocommerce_order_fully_refunded', array(
+ &$this, 'order_fully_refunded'
+ ));
+
+ add_action('woocommerce_order_status_cancelled', array(
+ &$this, 'order_canceled'
+ ));
+ }
+
+ /**
+ * @param WC_Subscription $wc_subscription
+ * @param string $new_status
+ * @param string $old_status
+ */
+ public function filter_pre_status($wc_subscription, $new_status, $old_status)
+ {
+ switch ($new_status) {
+ case 'on-hold':
+ $this->suspend_status($wc_subscription);
+ break;
+ case 'active':
+ $this->active_status($wc_subscription, $old_status);
+ break;
+ case 'cancelled':
+ $this->cancelled_status($wc_subscription);
+ break;
+ case 'pending-cancel':
+ if (!$this->vindi_settings->dependencies->is_wc_memberships_active()) {
+ $wc_subscription->update_status('cancelled');
+ }
+ break;
+ }
+ }
+
+ /**
+ * @param WC_Subscription $wc_subscription
+ */
+ public function suspend_status($wc_subscription)
+ {
+ $subscription_id = $this->get_vindi_subscription_id($wc_subscription);
+ if ($this->vindi_settings->get_synchronism_status()) {
+ $this->routes->suspendSubscription($subscription_id);
+ }
+ }
+
+ /**
+ * @param WC_Subscription $wc_subscription
+ */
+ public function cancelled_status($wc_subscription)
+ {
+ $subscription_id = $this->get_vindi_subscription_id($wc_subscription);
+ if ($this->routes->isSubscriptionActive($subscription_id)) {
+ $this->routes->suspendSubscription($subscription_id, true);
+ }
+ }
+
+ /**
+ * @param WC_Subscription $wc_subscription
+ */
+ public function active_status($wc_subscription, $old_status)
+ {
+ if ('pending' == $old_status)
+ return;
+ $subscription_id = $this->get_vindi_subscription_id($wc_subscription);
+ if ($this->vindi_settings->get_synchronism_status()
+ && !$this->routes->isSubscriptionActive($subscription_id)) {
+ $this->routes->activateSubscription($subscription_id);
+ }
+ }
+
+ /**
+ * @param WC_Subscription $wc_subscription
+ */
+ public function get_vindi_subscription_id($wc_subscription)
+ {
+ $subscription_id = method_exists($wc_subscription, 'get_id')
+ ? $wc_subscription->get_id()
+ : $wc_subscription->id;
+ return get_post_meta($subscription_id, 'vindi_subscription_id', true);
+ }
+
+ /**
+ * @param WC_Order $order
+ */
+ public function order_fully_refunded($order)
+ {
+ if (!is_object($order)) {
+ $order = wc_get_order($order);
+ }
+
+ if (wcs_order_contains_subscription($order, array('parent', 'renewal'))) {
+ $subscriptions = wcs_get_subscriptions_for_order(wcs_get_objects_property($order, 'id'), array('order_type' => array('parent', 'renewal')));
+ foreach ($subscriptions as $subscription) {
+ $latest_order = $subscription->get_last_order();
+
+ if (wcs_get_objects_property($order, 'id') == $latest_order && $subscription->can_be_updated_to('cancelled')) {
+ // translators: $1: opening link tag, $2: order number, $3: closing link tag
+ $subscription->update_status(
+ 'cancelled',
+ wp_kses(sprintf(
+ __('A assinatura foi cancelada pelo pedido reembolsado %1$s#%2$s%3$s.', VINDI),
+ sprintf('', esc_url(wcs_get_edit_post_link(wcs_get_objects_property($order, 'id')))),
+ $order->get_order_number(),
+ ' '
+ ), array('a' => array('href' => true))));
+ }
+ }
+ }
+ }
+
+ /**
+ * @param WC_Order $order
+ */
+ public function order_canceled($order)
+ {
+ if (!is_object($order)) {
+ $order = wc_get_order($order);
+ }
+
+ $vindi_order = get_post_meta($order->id, 'vindi_order', true);
+ if(!is_array($vindi_order)) {
+ return;
+ }
+ $single_payment_bill_id = 0;
+ foreach ($vindi_order as $key => $item) {
+ if($key == 'single_payment' && $vindi_order[$key]['bill']['status'] != 'canceled') {
+ $single_payment_bill_id = $vindi_order[$key]['bill']['id'];
+ }
+ $vindi_order[$key]['bill']['status'] = 'canceled';
+ }
+ update_post_meta($order->id, 'vindi_order', $vindi_order);
+ if($single_payment_bill_id) {
+ $this->routes->deleteBill($single_payment_bill_id);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/utils/WcAddGateway.php b/src/utils/WcAddGateway.php
deleted file mode 100644
index ca420ee..0000000
--- a/src/utils/WcAddGateway.php
+++ /dev/null
@@ -1,18 +0,0 @@
- 'PHP',
+ 'version' => [
+ 'validation' => '>=',
+ 'number' => VINDI_MININUM_PHP_VERSION
+ ]
+ ],
+ [
+ 'name' => 'WordPress',
+ 'version' => [
+ 'validation' => '>=',
+ 'number' => VINDI_MININUM_WP_VERSION
+ ]
+ ]
+ ];
+
+ $errors = [];
+
+ foreach ($critical_dependencies as $dependency) {
+ $version = $dependency['version'];
+ if (!version_compare(PHP_VERSION, $version['number'], $version['validation'])) {
+ $name = $dependency['name'];
+ $number = $version['number'];
+ $notice = function () use ($name, $number) {
+ self::critical_dependency_missing_notice($name, $number);
+ };
+ add_action(
+ 'admin_notices',
+ $notice
+ );
+ array_push($errors, $plugin);
+ }
+ }
+ if(!empty($errors)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Check required plugins
+ *
+ * @return boolean
+ */
+ public static function check()
+ {
+ if(!self::check_critical_dependencies()) {
+ return false;
+ }
+
+ if (!self::$active_plugins) {
+ self::init();
+ }
+ if (current_user_can('install_plugins')) {
+ $woocommerce_url = wp_nonce_url(self_admin_url('update.php?action=install-plugin&plugin=woocommerce'), 'install-plugin_woocommerce');
+ $ecfb_url = wp_nonce_url(self_admin_url('update.php?action=install-plugin&plugin=woocommerce-extra-checkout-fields-for-brazil'), 'install-plugin_woocommerce-extra-checkout-fields-for-brazil');
+ } else {
+ $woocommerce_url = 'https://wordpress.org/extend/plugins/woocommerce/';
+ $ecfb_url = 'https://wordpress.org/extend/plugins/woocommerce-extra-checkout-fields-for-brazil/';
+ }
+
+ $required_plugins = [
+ [
+ 'path' => 'woocommerce/woocommerce.php',
+ 'plugin' => [
+ 'name' => 'WooCommerce',
+ 'url' => $woocommerce_url,
+ 'version' => [
+ 'validation' => '>=',
+ 'number' => '3.0'
+ ]
+ ]
+ ],
+ [
+ 'path' => 'woocommerce-extra-checkout-fields-for-brazil/woocommerce-extra-checkout-fields-for-brazil.php',
+ 'plugin' => [
+ 'name' => 'Brazilian Market on WooCommerce',
+ 'url' => $ecfb_url,
+ 'version' => [
+ 'validation' => '>=',
+ 'number' => '3.5'
+ ]
+ ]
+ ]
+ ];
+
+ self::is_wc_subscriptions_active();
+
+ $errors = [];
+
+ foreach ($required_plugins as $plugin) {
+ if (self::is_plugin_active($plugin) == false) {
+ $name = $plugin['plugin']['name'];
+ $number = $plugin['plugin']['version']['number'];
+ $url = $plugin['plugin']['url'];
+ $notice = function () use ($name, $number, $url) {
+ self::missing_notice($name, $number, $url);
+ };
+ add_action(
+ 'admin_notices',
+ $notice
+ );
+
+ array_push($errors, $plugin);
+ }
+
+ if (!defined('VINDI_TESTS') && self::verify_plugin_version($plugin) == false) {
+ array_push($errors, $plugin);
+ }
+ }
+
+ if(!empty($errors)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Generate notice content
+ *
+ * @param string $name Plugin name
+ * @param string $version Plugin version
+ * @param string $link Plugin url
+ *
+ * @return string
+ */
+ public static function missing_notice($name, $version, $link)
+ {
+ include plugin_dir_path(VINDI_SRC) . 'src/views/missing-dependency.php';
+ }
+
+ /**
+ * Generate critical dependency notice content
+ *
+ * @param string $name Dependency name
+ * @param string $version Dependency version
+ *
+ * @return string
+ */
+ public static function critical_dependency_missing_notice($name, $version)
+ {
+ include plugin_dir_path(VINDI_SRC) . 'src/views/missing-critical-dependency.php';
+ }
+
+ /**
+ * Check if the plugin is active
+ *
+ * @param array $plugin
+ *
+ * @return boolean
+ */
+ public static function is_plugin_active($plugin)
+ {
+ if(in_array($plugin['path'], self::$active_plugins) && is_plugin_active($plugin['path'])) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Check if the current version of the plugin is at least the minimum required version
+ *
+ * @param array $plugin
+ *
+ * @return boolean
+ */
+ public static function verify_plugin_version($plugin)
+ {
+ $plugin_data = get_plugin_data(WP_PLUGIN_DIR . "/" . $plugin['path']);
+ $version_match = $plugin['plugin']['version'];
+ $version_compare = version_compare(
+ $plugin_data['Version'],
+ $version_match['number'],
+ $version_match['validation']
+ );
+
+ if ($version_compare == false) {
+ $name = $plugin['plugin']['name'];
+ $number = $version_match['number'];
+ $url = $plugin['plugin']['url'];
+ $notice = function () use ($name, $number, $url) {
+ self::missing_notice($name, $number, $url);
+ };
+ add_action(
+ 'admin_notices',
+ $notice
+ );
+
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Check if WC Subscriptions is active
+ *
+ * @return boolean
+ */
+ public static function is_wc_subscriptions_active()
+ {
+ $wc_subscriptions = [
+ 'path' => 'woocommerce-subscriptions/woocommerce-subscriptions.php',
+ 'plugin' => [
+ 'name' => 'WooCommerce Subscriptions',
+ 'url' => 'http://www.woothemes.com/products/woocommerce-subscriptions/',
+ 'version' => [
+ 'validation' => '>=',
+ 'number' => '2.2'
+ ]
+ ],
+ ];
+
+ return self::is_plugin_active($wc_subscriptions) || class_exists('WC_Subscriptions');
+ }
+
+ /**
+ * Check if WC Memberships is active
+ *
+ * @return boolean
+ */
+ public static function is_wc_memberships_active()
+ {
+ $wc_memberships = [
+ 'path' => 'woocommerce-memberships/woocommerce-memberships.php',
+ 'plugin' => [
+ 'name' => 'WooCommerce Memberships',
+ 'url' => 'http://www.woothemes.com/products/woocommerce-memberships/'
+ ]
+ ];
+ if(self::is_plugin_active($wc_memberships)) {
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/src/validators/DependenciesNotices.php b/src/validators/DependenciesNotices.php
deleted file mode 100644
index 6fd113b..0000000
--- a/src/validators/DependenciesNotices.php
+++ /dev/null
@@ -1,53 +0,0 @@
-
-
-
-
: ' . __( 'WooCommerce Extra Checkout Fields for Brazil', 'vindi-woocommerce' ) . '' ); ?>
-
diff --git a/src/views/invalid-api-key.php b/src/views/invalid-api-key.php
new file mode 100644
index 0000000..be3248a
--- /dev/null
+++ b/src/views/invalid-api-key.php
@@ -0,0 +1,14 @@
+
+
+
diff --git a/src/views/missing-critical-dependency.php b/src/views/missing-critical-dependency.php
new file mode 100644
index 0000000..69d661a
--- /dev/null
+++ b/src/views/missing-critical-dependency.php
@@ -0,0 +1,22 @@
+
+
+
+
diff --git a/src/views/missing-dependency.php b/src/views/missing-dependency.php
new file mode 100644
index 0000000..7b3977a
--- /dev/null
+++ b/src/views/missing-dependency.php
@@ -0,0 +1,21 @@
+
+
+
diff --git a/src/views/php-version-missing.php b/src/views/php-version-missing.php
deleted file mode 100644
index b1a6cdf..0000000
--- a/src/views/php-version-missing.php
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
diff --git a/src/views/product-status.php b/src/views/product-status.php
new file mode 100644
index 0000000..eb170b1
--- /dev/null
+++ b/src/views/product-status.php
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/woocommerce-missing.php b/src/views/woocommerce-missing.php
deleted file mode 100644
index 7c55647..0000000
--- a/src/views/woocommerce-missing.php
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
: ' . __( 'WooCommerce', 'vindi-woocommerce' ) . '' ); ?>
-
diff --git a/src/views/wp-version-missing.php b/src/views/wp-version-missing.php
deleted file mode 100644
index 443b782..0000000
--- a/src/views/wp-version-missing.php
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
index 4f6265f..d086df5 100755
--- a/tests/bootstrap.php
+++ b/tests/bootstrap.php
@@ -111,16 +111,13 @@ public function includes_wc()
// framework
require_once $wc_tests_framework_base_dir . 'class-wc-unit-test-factory.php';
require_once $wc_tests_framework_base_dir . 'class-wc-mock-session-handler.php';
- require_once $wc_tests_framework_base_dir . 'class-wc-mock-wc-data.php';
- require_once $wc_tests_framework_base_dir . 'class-wc-mock-wc-object-query.php';
- require_once $wc_tests_framework_base_dir . 'class-wc-mock-payment-gateway.php';
- require_once $wc_tests_framework_base_dir . 'class-wc-payment-token-stub.php';
- require_once $wc_tests_framework_base_dir . 'vendor/class-wp-test-spy-rest-server.php';
+ // require_once $wc_tests_framework_base_dir . 'class-wc-mock-wc-data.php';
+ // require_once $wc_tests_framework_base_dir . 'class-wc-mock-wc-object-query.php';
+ // require_once $wc_tests_framework_base_dir . 'class-wc-mock-payment-gateway.php';
+ // require_once $wc_tests_framework_base_dir . 'class-wc-payment-token-stub.php';
+ // require_once $wc_tests_framework_base_dir . 'vendor/class-wp-test-spy-rest-server.php';
- // test case
- require_once $wc_tests_framework_base_dir . 'class-wc-unit-test-case.php';
-
// Helpers
require_once $wc_tests_framework_base_dir . 'helpers/class-wc-helper-product.php';
require_once $wc_tests_framework_base_dir . 'helpers/class-wc-helper-coupon.php';
@@ -128,9 +125,12 @@ public function includes_wc()
require_once $wc_tests_framework_base_dir . 'helpers/class-wc-helper-shipping.php';
require_once $wc_tests_framework_base_dir . 'helpers/class-wc-helper-customer.php';
require_once $wc_tests_framework_base_dir . 'helpers/class-wc-helper-order.php';
- require_once $wc_tests_framework_base_dir . 'helpers/class-wc-helper-shipping-zones.php';
- require_once $wc_tests_framework_base_dir . 'helpers/class-wc-helper-payment-token.php';
- require_once $wc_tests_framework_base_dir . 'helpers/class-wc-helper-settings.php';
+ // require_once $wc_tests_framework_base_dir . 'helpers/class-wc-helper-shipping-zones.php';
+ // require_once $wc_tests_framework_base_dir . 'helpers/class-wc-helper-payment-token.php';
+ // require_once $wc_tests_framework_base_dir . 'helpers/class-wc-helper-settings.php';
+
+ // test case
+ // require_once $wc_tests_framework_base_dir . 'class-wc-unit-test-case.php';
}
/**
diff --git a/tests/phpunit/vindi/admin/test-api.php b/tests/phpunit/vindi/admin/test-api.php
index 0c8f720..38c7c9b 100644
--- a/tests/phpunit/vindi/admin/test-api.php
+++ b/tests/phpunit/vindi/admin/test-api.php
@@ -1,12 +1,68 @@
assertTrue(true);
+ return;
+ }
+
+ $logger = new VindiLogger(VINDI, false);
+
+ $api = new VindiApi('', $logger, '');
+
+ $this->assertEquals('1', $api->test_api_key(API_TOKEN));
+ }
+
+ /**
+ * This test validates the data used for the creation
+ * orders within the Vindi API
+ */
+
+ public function test_create_customer_within_vindi()
+ {
+
+ if(!defined('API_TEST') || !API_TEST) return;
+
+ $vindi_customer_id = get_user_meta(1, 'vindi_customer_id');
+
+ // Check meta Vindi ID
+ if (empty($vindi_customer_id)) {
+
+
+ }
+
+ }
+
+ /**
+ * Este teste valida os dados utilizados para a criação
+ * de um plano da API da Vindi
+ */
+ public function test_create_info_plan_within_vindi()
{
$this->assertTrue(true);
}
diff --git a/tests/phpunit/vindi/base/test-bootstrap.php b/tests/phpunit/vindi/base/test-bootstrap.php
index f5cd242..76b14ed 100644
--- a/tests/phpunit/vindi/base/test-bootstrap.php
+++ b/tests/phpunit/vindi/base/test-bootstrap.php
@@ -16,8 +16,8 @@ public static function setUpBeforeClass()
WC_Vindi_Payment::instance();
// Run fake actions
- do_action('init');
do_action('plugins_loaded');
+ do_action('init');
}
diff --git a/tests/phpunit/vindi/base/test-customer.php b/tests/phpunit/vindi/base/test-customer.php
new file mode 100644
index 0000000..3a231be
--- /dev/null
+++ b/tests/phpunit/vindi/base/test-customer.php
@@ -0,0 +1,104 @@
+mock_data = array(
+ 'id' => 328876,
+ 'name' => 'Pedro Vicente Nicolas Pereira',
+ 'email' => 'pedro.vicente@woo.local',
+ 'code' => 'WC-USER-2',
+ 'status' => 'active',
+ 'address' => array(
+ 'street' => 'Rua Joaquim Mendes do Prado',
+ 'number' => '927',
+ 'additional_details' => 'Casa 1',
+ 'zipcode' => '03379-030',
+ 'neighborhood' => 'Vila Olinda',
+ 'city' => 'São Paulo',
+ 'state' => 'SP',
+ 'country' => 'BR'
+ ),
+ 'phones' => array(
+ array(
+ 'id' => 6874,
+ 'phone_type' => 'landline',
+ 'number' => '551125939005'
+ )
+ ),
+ 'registry_code' => '274.587.488-85',
+ 'notes' => '',
+ 'metadata' => '',
+ 'created_at' => '2020-06-08T11:20:57.000-03:00',
+ 'updated_at' => '2020-06-08T11:20:57.000-03:00'
+ );
+
+ $this->customer = WC_Helper_Customer::create_customer();
+ update_user_meta($this->customer->get_id(), 'vindi_customer_id', 328876);
+ }
+
+ public function tearDown()
+ {
+ $this->mock_data = null;
+ wp_delete_user($this->customer->get_id());
+ wc_delete_user_data($this->customer->get_id());
+ $this->customer = null;
+ }
+
+ public function test_create_customer()
+ {
+ $routes = $this->createMock(VindiRoutes::class);
+ $routes->method('createCustomer')->willReturn($this->mock_data);
+ $settings = $this->createMock(VindiSettings::class);
+ $settings->routes = $routes;
+
+ $customer_controller = new CustomerController($settings);
+
+ $createdUser = $customer_controller->create($this->customer->get_id());
+ $this->assertEquals($createdUser, $this->mock_data);
+ }
+
+ public function test_update_customer()
+ {
+ $routes = $this->createMock(VindiRoutes::class);
+ $routes->method('createCustomer')->willReturn($this->mock_data);
+ $routes->method('findCustomerById')->willReturn($this->mock_data);
+ $routes->method('updateCustomer')->willReturn($this->mock_data);
+ $settings = $this->createMock(VindiSettings::class);
+ $settings->routes = $routes;
+
+ $customer_controller = new CustomerController($settings);
+
+ $createdUser = $customer_controller->update($this->customer->get_id());
+ $this->assertEquals($createdUser, $this->mock_data);
+ }
+
+ public function test_delete_customer()
+ {
+ $deleted_data = $this->mock_data;
+ $deleted_data['status'] = 'archived';
+ $routes = $this->createMock(VindiRoutes::class);
+ $routes->method('findCustomerById')->willReturn($this->mock_data);
+ $routes->method('createCustomer')->willReturn($this->mock_data);
+ $routes->method('deleteCustomer')->willReturn($deleted_data);
+ $settings = $this->createMock(VindiSettings::class);
+ $settings->routes = $routes;
+
+ $customer_controller = new CustomerController($settings);
+
+ $deletedUser = $customer_controller->delete($this->customer->get_id());
+ $this->assertEquals($deletedUser, $deleted_data);
+ }
+}; ?>
\ No newline at end of file
diff --git a/tests/phpunit/vindi/base/test-dependencies.php b/tests/phpunit/vindi/base/test-dependencies.php
new file mode 100644
index 0000000..b1ecc92
--- /dev/null
+++ b/tests/phpunit/vindi/base/test-dependencies.php
@@ -0,0 +1,13 @@
+assertTrue(VindiDependencies::check_critical_dependencies());
+ }
+}; ?>
\ No newline at end of file
diff --git a/tests/phpunit/vindi/base/test-gateways.php b/tests/phpunit/vindi/base/test-gateways.php
new file mode 100644
index 0000000..b360158
--- /dev/null
+++ b/tests/phpunit/vindi/base/test-gateways.php
@@ -0,0 +1,21 @@
+assertEquals('cc', $credit_card->type());
+ $this->assertEquals('bank_slip', $bank_slip->type());
+ }
+}; ?>
\ No newline at end of file
diff --git a/tests/phpunit/vindi/base/test-initial-subscription.php b/tests/phpunit/vindi/base/test-initial-subscription.php
index 6ab5a42..462c6ab 100644
--- a/tests/phpunit/vindi/base/test-initial-subscription.php
+++ b/tests/phpunit/vindi/base/test-initial-subscription.php
@@ -1,12 +1,14 @@
save();
// Arrange: Set up an order with:
- // 1) A variation product.
- // 2) The same product added several times.
+ // 1) A variation product. // 2) The same product added several times.
// 3) A valid BR ZIP code
$order = new WC_Order();
@@ -56,7 +57,11 @@ function test_data_for_mutli_item_order()
$order->calculate_totals();
// Act: Call get_level3_data_from_order().
- $gateway = new VindiCreditGateway();
+
+ $settings = new VindiSettings();
+ $controllers = new VindiControllers($settings);
+
+ $gateway = new VindiCreditGateway($settings, $controllers);
$result = $gateway->get_level3_data_from_order($order);
@@ -102,4 +107,57 @@ public function test_pre_30_postal_code_omission()
$gateway = new VindiHelpers();
$this->assertEquals(array(), $gateway->get_level3_data_from_order($order));
}
+
+ public function test_non_us_shipping_zip_codes()
+ {
+ // Skip this test because of the complexity of creating products in WC pre-3.0.
+ if (VindiHelpers::is_wc_lt('3.0')) {
+ // Dummy assertion.
+ $this->assertEquals(VindiHelpers::is_wc_lt('3.0'), true);
+ return;
+ }
+
+ // Update the store with the right post code.
+ update_option('woocommerce_store_postcode', 1040);
+
+ // Arrange: Create a couple of products to use.
+ $product = WC_Helper_Product::create_simple_product();
+ $product->set_regular_price(19.19);
+ $product->save();
+
+ // Arrange: Set up an order with a non-US postcode.
+ $order = new WC_Order();
+ $order->set_shipping_postcode('1050');
+ $order->add_product($product, 1);
+ $order->save();
+ $order->calculate_totals();
+
+ // Act: Call get_level3_data_from_order().
+ $store_postcode = '1100';
+
+ $settings = new VindiSettings();
+ $controllers = new VindiControllers($settings);
+
+ $gateway = new VindiCreditGateway($settings, $controllers);
+ $result = $gateway->get_level3_data_from_order($order);
+
+ // Assert.
+ $this->assertEquals(
+ array(
+ 'merchant_reference' => $order->get_id(),
+ 'shipping_amount' => 0,
+ 'line_items' => array(
+ (object) array(
+ 'product_code' => (string) $product->get_id(),
+ 'product_description' => substr($product->get_name(), 0, 26),
+ 'unit_cost' => 1919,
+ 'quantity' => 1,
+ 'tax_amount' => 0,
+ 'discount_amount' => 0,
+ ),
+ ),
+ ),
+ $result
+ );
+ }
};
diff --git a/tests/phpunit/vindi/base/test-payment-processor.php b/tests/phpunit/vindi/base/test-payment-processor.php
new file mode 100644
index 0000000..a29eb2a
--- /dev/null
+++ b/tests/phpunit/vindi/base/test-payment-processor.php
@@ -0,0 +1,114 @@
+mock_shipping_product = array(
+ 'id' => 465,
+ 'name' => 'Frete (Flat rate)',
+ 'code' => 'flat-rate',
+ 'unit' => '',
+ 'status' => 'active',
+ 'description' => '',
+ 'invoice' => 'always',
+ 'created_at' => '2020-06-08T11:20:57.000-03:00',
+ 'updated_at' => '2020-06-08T11:20:57.000-03:00',
+ 'pricing_schema' => array(
+ 'id' => 'string',
+ 'short_format' => 'R$ 10,0',
+ 'price' => 10.0,
+ 'minimum_price' => null,
+ 'schema_type' => 'flat',
+ 'pricing_ranges' => array(),
+ 'created_at' => '2020-06-08T11:20:57.000-03:00'
+ ),
+ 'metadata' => array()
+ );
+ $this->customer = WC_Helper_Customer::create_customer();
+ update_user_meta($this->customer->get_id(), 'vindi_customer_id', 328876);
+ }
+
+ public function tearDown()
+ {
+ $this->mock_shipping_product = null;
+ wp_delete_user($this->customer->get_id());
+ wc_delete_user_data($this->customer->get_id());
+ $this->customer = null;
+ }
+
+ public function test_processor_build_items()
+ {
+ $routes = $this->createMock(VindiRoutes::class);
+ $routes->method('findOrCreateProduct')->willReturn($this->mock_shipping_product);
+ $settings = new VindiSettings();
+ $settings->routes = $routes;
+ $controllers = new VindiControllers($settings);
+
+ $credit_card = new VindiCreditGateway($settings, $controllers);
+
+ // Skip this test because of the complexity of creating products in WC pre-3.0.
+ if (VindiHelpers::is_wc_lt('3.0')) {
+ // Dummy assertion.
+ $this->assertEquals(VindiHelpers::is_wc_lt('3.0'), true);
+ return;
+ }
+
+ // Arrange: Create a couple of products to use.
+ // $simple_product = WC_Helper_Product::create_simple_product();
+
+ $product = new WC_Product();
+
+ $product->set_name('teste 123');
+
+ $product->set_price(20);
+ $product->save();
+
+ update_post_meta($product->get_id(), 'vindi_product_id', 1239812039);
+
+ // Arrange: Set up an order
+ $order = WC_Helper_Order::create_order($this->customer->get_id());
+ $order->remove_order_items('line_item');
+
+ $order->add_product($product, 4, array(
+ 'subtotal' => 80,
+ 'total' => 80
+ ));
+
+ $order->set_payment_method($credit_card->id);
+ $order->set_payment_method_title($credit_card->method_title);
+ $order->save();
+ $order->calculate_totals();
+
+ $payment_processor = new VindiPaymentProcessor($order, $credit_card, $settings, $controllers);
+ $bill_items = $payment_processor->build_product_items('bill', $order->get_items());
+
+ $this->assertEquals(
+ array(
+ array(
+ 'product_id' => 1239812039,
+ 'quantity' => 4,
+ 'pricing_schema' => array(
+ 'price' => 20.0,
+ 'schema_type' => 'per_unit'
+ )
+ ),
+ array(
+ 'product_id' => 465,
+ 'amount' => 10.0
+ )
+ ),
+ $bill_items
+ );
+ }
+}; ?>
\ No newline at end of file
diff --git a/tests/phpunit/vindi/base/test-product.php b/tests/phpunit/vindi/base/test-product.php
new file mode 100644
index 0000000..e3e3a01
--- /dev/null
+++ b/tests/phpunit/vindi/base/test-product.php
@@ -0,0 +1,138 @@
+mock_data = array(
+ 'id' => 463389,
+ 'name' => '[WC] Dummy Product',
+ 'code' => 'WC-10',
+ 'unit' => null,
+ 'status' => 'active',
+ 'description' => '',
+ 'invoice' => 'always',
+ 'created_at' => '2020-06-08T11:20:57.000-03:00',
+ 'updated_at' => '2020-06-08T11:20:57.000-03:00',
+ 'pricing_schema' => array(
+ 'id' => 'string',
+ 'short_format' => 'R$ 10,0',
+ 'price' => 10.0,
+ 'minimum_price' => null,
+ 'schema_type' => 'flat',
+ 'pricing_ranges' => array(),
+ 'created_at' => '2020-06-08T11:20:57.000-03:00'
+ ),
+ 'metadata' => array()
+ );
+ }
+
+ public function tearDown()
+ {
+ $this->mock_data = null;
+ }
+
+ public function test_create_product()
+ {
+ // Skip this test because of the complexity of creating products in WC pre-3.0.
+ if (VindiHelpers::is_wc_lt('3.0')) {
+ // Dummy assertion.
+ $this->assertEquals(VindiHelpers::is_wc_lt('3.0'), true);
+ return;
+ }
+
+ $routes = $this->createMock(VindiRoutes::class);
+ $routes->method('createProduct')->willReturn($this->mock_data);
+ $settings = $this->createMock(VindiSettings::class);
+ $settings->routes = $routes;
+
+ $product_controller = new ProductController($settings);
+
+ $product = WC_Helper_Product::create_simple_product();
+ delete_post_meta($product->get_id(), 'vindi_product_id');
+
+ $createdProduct = $product_controller->create($product->get_id(), '', '', true);
+ $this->assertEquals($createdProduct, $this->mock_data);
+ }
+
+ public function test_update_product()
+ {
+ // Skip this test because of the complexity of creating products in WC pre-3.0.
+ if (VindiHelpers::is_wc_lt('3.0')) {
+ // Dummy assertion.
+ $this->assertEquals(VindiHelpers::is_wc_lt('3.0'), true);
+ return;
+ }
+
+ $routes = $this->createMock(VindiRoutes::class);
+ $routes->method('createProduct')->willReturn($this->mock_data);
+ $routes->method('updateProduct')->willReturn($this->mock_data);
+ $settings = $this->createMock(VindiSettings::class);
+ $settings->routes = $routes;
+
+ $product_controller = new ProductController($settings);
+
+ $product = WC_Helper_Product::create_simple_product();
+
+ $createdProduct = $product_controller->update($product->get_id(), '', '', true);
+ $this->assertEquals($createdProduct, $this->mock_data);
+ }
+
+ public function test_untrash_product()
+ {
+ // Skip this test because of the complexity of creating products in WC pre-3.0.
+ if (VindiHelpers::is_wc_lt('3.0')) {
+ // Dummy assertion.
+ $this->assertEquals(VindiHelpers::is_wc_lt('3.0'), true);
+ return;
+ }
+
+ $routes = $this->createMock(VindiRoutes::class);
+ $routes->method('createProduct')->willReturn($this->mock_data);
+ $routes->method('updateProduct')->willReturn($this->mock_data);
+ $settings = $this->createMock(VindiSettings::class);
+ $settings->routes = $routes;
+
+ $product_controller = new ProductController($settings);
+
+ $product = WC_Helper_Product::create_simple_product();
+
+ $createdProduct = $product_controller->untrash($product->get_id());
+ $this->assertEquals($createdProduct, $this->mock_data);
+ }
+
+ public function test_trash_product()
+ {
+ // Skip this test because of the complexity of creating products in WC pre-3.0.
+ if (VindiHelpers::is_wc_lt('3.0')) {
+ // Dummy assertion.
+ $this->assertEquals(VindiHelpers::is_wc_lt('3.0'), true);
+ return;
+ }
+
+ $trash_data = $this->mock_data;
+ $trash_data['status'] = 'inactive';
+
+ $routes = $this->createMock(VindiRoutes::class);
+ $routes->method('createProduct')->willReturn($this->mock_data);
+ $routes->method('updateProduct')->willReturn($trash_data);
+ $settings = $this->createMock(VindiSettings::class);
+ $settings->routes = $routes;
+
+ $product_controller = new ProductController($settings);
+
+ $product = WC_Helper_Product::create_simple_product();
+
+ $createdProduct = $product_controller->trash($product->get_id());
+ $this->assertEquals($createdProduct, $trash_data);
+ }
+}; ?>
\ No newline at end of file
diff --git a/tests/phpunit/vindi/base/test-vindi.php b/tests/phpunit/vindi/base/test-vindi.php
index 0383a14..04631da 100644
--- a/tests/phpunit/vindi/base/test-vindi.php
+++ b/tests/phpunit/vindi/base/test-vindi.php
@@ -1,7 +1,7 @@
assertTrue(defined('VINDI'));
$this->assertTrue(defined('VINDI_MININUM_WP_VERSION'));
$this->assertTrue(defined('VINDI_MININUM_PHP_VERSION'));
- $this->assertTrue(defined('VINDI__FILE__'));
+ $this->assertTrue(defined('VINDI_FILE'));
$this->assertTrue(defined('VINDI_PLUGIN_BASE'));
$this->assertTrue(defined('VINDI_PATH'));
}
diff --git a/vindi.php b/vindi.php
index 0d1ddb0..181efc7 100644
--- a/vindi.php
+++ b/vindi.php
@@ -1,14 +1,14 @@
=')) {
- add_action('admin_notices', 'vindi_fail_php_version');
-} elseif (!version_compare(get_bloginfo('version'), VINDI_MININUM_WP_VERSION, '>=')) {
- add_action('admin_notices', 'vindi_fail_wp_version');
-} else {
+require_once plugin_dir_path(__FILE__) . '/src/validators/Dependencies.php';
- require_once VINDI_PATH . 'src/VindiWoocommerce.php';
+require_once VINDI_PATH . 'src/VindiWoocommerce.php';
- if (!defined('VINDI_TESTS')) {
- // In tests we run the instance manually.
- $GLOBALS['vindi'] = WC_Vindi_Payment::instance();
- }
+if (!defined('VINDI_TESTS')) {
+ // In tests we run the instance manually.
+ // $GLOBALS['vindi'] = WC_Vindi_Payment::get_instance();
}
diff --git a/yarn.lock b/yarn.lock
index f82c229..f82aae7 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -9,21 +9,26 @@
dependencies:
"@babel/highlight" "^7.8.3"
+"@babel/helper-validator-identifier@^7.9.0":
+ version "7.9.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz#90977a8e6fbf6b431a7dc31752eee233bf052d80"
+ integrity sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g==
+
"@babel/highlight@^7.8.3":
- version "7.8.3"
- resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.8.3.tgz#28f173d04223eaaa59bc1d439a3836e6d1265797"
- integrity sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==
+ version "7.9.0"
+ resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.9.0.tgz#4e9b45ccb82b79607271b2979ad82c7b68163079"
+ integrity sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==
dependencies:
+ "@babel/helper-validator-identifier" "^7.9.0"
chalk "^2.0.0"
- esutils "^2.0.2"
js-tokens "^4.0.0"
-"@babel/runtime@^7.6.3":
- version "7.8.4"
- resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.8.4.tgz#d79f5a2040f7caa24d53e563aad49cbc05581308"
- integrity sha512-neAp3zt80trRVBI1x0azq6c57aNBqYZH8KhMm3TaB7wEI5Q4A2SHfBHE8w9gOhI/lrqxtEbXZgQIrHP+wvSGwQ==
+"@babel/runtime@^7.9.2":
+ version "7.9.2"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.2.tgz#d90df0583a3a252f09aaa619665367bae518db06"
+ integrity sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==
dependencies:
- regenerator-runtime "^0.13.2"
+ regenerator-runtime "^0.13.4"
"@commitlint/cli@^8.3.5":
version "8.3.5"
@@ -442,10 +447,10 @@ chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.4.1, chalk@^2.4.2, chalk@~2.4.
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
-chalk@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4"
- integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==
+chalk@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.0.0.tgz#6e98081ed2d17faab615eb52ac66ec1fe6209e72"
+ integrity sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==
dependencies:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
@@ -478,9 +483,9 @@ cli-cursor@^2.1.0:
restore-cursor "^2.0.0"
cli-width@^2.0.0:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
- integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48"
+ integrity sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==
coffeescript@~1.10.0:
version "1.10.0"
@@ -525,9 +530,9 @@ colors@~1.1.2:
integrity sha1-FopHAXVran9RoSzgyXv6KMCE7WM=
commitizen@^4.0.3:
- version "4.0.3"
- resolved "https://registry.yarnpkg.com/commitizen/-/commitizen-4.0.3.tgz#c19a4213257d0525b85139e2f36db7cc3b4f6dae"
- integrity sha512-lxu0F/Iq4dudoFeIl5pY3h3CQJzkmQuh3ygnaOvqhAD8Wu2pYBI17ofqSuPHNsBTEOh1r1AVa9kR4Hp0FAHKcQ==
+ version "4.0.4"
+ resolved "https://registry.yarnpkg.com/commitizen/-/commitizen-4.0.4.tgz#60e9666e293269f459f1038ca452b39acecb8999"
+ integrity sha512-gfEt1rDE9VqKif+LE3cAThpqiW/1K3c2Nx83jSU6ohZjQd2CAmz1rMIlgmbPrPagOkKZw7USzSVubS758ZTWdA==
dependencies:
cachedir "2.2.0"
cz-conventional-changelog "3.0.1"
@@ -540,7 +545,7 @@ commitizen@^4.0.3:
inquirer "6.5.0"
is-utf8 "^0.2.1"
lodash "4.17.15"
- minimist "1.2.0"
+ minimist "1.2.3"
shelljs "0.7.6"
strip-bom "4.0.0"
strip-json-comments "3.0.1"
@@ -553,7 +558,7 @@ compare-func@^1.3.1:
array-ify "^1.0.0"
dot-prop "^3.0.0"
-compare-versions@^3.5.1:
+compare-versions@^3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.6.0.tgz#1a5689913685e5a87637b8d3ffca75514ec41d62"
integrity sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==
@@ -788,11 +793,6 @@ esprima@^4.0.0:
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
-esutils@^2.0.2:
- version "2.0.3"
- resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
- integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
-
eventemitter2@~0.4.13:
version "0.4.14"
resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-0.4.14.tgz#8f61b75cde012b2e9eb284d4545583b5643b61ab"
@@ -1133,9 +1133,9 @@ grunt-legacy-util@~1.1.1:
which "~1.3.0"
grunt@^1.0.4:
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/grunt/-/grunt-1.0.4.tgz#c799883945a53a3d07622e0737c8f70bfe19eb38"
- integrity sha512-PYsMOrOC+MsdGEkFVwMaMyc6Ob7pKmq+deg1Sjr+vvMWp35sztfwKE7qoN51V+UEtHsyNuMcGdgMLFkBHvMxHQ==
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/grunt/-/grunt-1.1.0.tgz#97dc6e6add901459774a988e4f454a12e24c9d3d"
+ integrity sha512-+NGod0grmviZ7Nzdi9am7vuRS/h76PcWDsV635mEXF0PEQMUV6Kb+OjTdsVxbi0PZmfQOjCMKb3w8CVZcqsn1g==
dependencies:
coffeescript "~1.10.0"
dateformat "~1.0.12"
@@ -1148,9 +1148,9 @@ grunt@^1.0.4:
grunt-legacy-log "~2.0.0"
grunt-legacy-util "~1.1.1"
iconv-lite "~0.4.13"
- js-yaml "~3.13.0"
+ js-yaml "~3.13.1"
minimatch "~3.0.2"
- mkdirp "~0.5.1"
+ mkdirp "~1.0.3"
nopt "~3.0.6"
path-is-absolute "~1.0.0"
rimraf "~2.6.2"
@@ -1209,18 +1209,18 @@ hooker@~0.2.3:
integrity sha1-uDT3I8xKJCqmWWNFnfbZhMXT2Vk=
hosted-git-info@^2.1.4:
- version "2.8.5"
- resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.5.tgz#759cfcf2c4d156ade59b0b2dfabddc42a6b9c70c"
- integrity sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==
+ version "2.8.8"
+ resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
+ integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==
husky@^4.2.3:
- version "4.2.3"
- resolved "https://registry.yarnpkg.com/husky/-/husky-4.2.3.tgz#3b18d2ee5febe99e27f2983500202daffbc3151e"
- integrity sha512-VxTsSTRwYveKXN4SaH1/FefRJYCtx+wx04sSVcOpD7N2zjoHxa+cEJ07Qg5NmV3HAK+IRKOyNVpi2YBIVccIfQ==
+ version "4.2.5"
+ resolved "https://registry.yarnpkg.com/husky/-/husky-4.2.5.tgz#2b4f7622673a71579f901d9885ed448394b5fa36"
+ integrity sha512-SYZ95AjKcX7goYVZtVZF2i6XiZcHknw50iXvY7b0MiGoj5RwdgRQNEHdb+gPDPCXKlzwrybjFjkL6FOj8uRhZQ==
dependencies:
- chalk "^3.0.0"
+ chalk "^4.0.0"
ci-info "^2.0.0"
- compare-versions "^3.5.1"
+ compare-versions "^3.6.0"
cosmiconfig "^6.0.0"
find-versions "^3.2.0"
opencollective-postinstall "^2.0.2"
@@ -1479,7 +1479,7 @@ js-tokens@^4.0.0:
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
-js-yaml@^3.13.1, js-yaml@~3.13.0:
+js-yaml@^3.13.1, js-yaml@~3.13.1:
version "3.13.1"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847"
integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==
@@ -1709,15 +1709,15 @@ minimist-options@^3.0.1:
arrify "^1.0.1"
is-plain-obj "^1.1.0"
-minimist@0.0.8:
- version "0.0.8"
- resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
- integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
+minimist@1.2.3:
+ version "1.2.3"
+ resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.3.tgz#3db5c0765545ab8637be71f333a104a965a9ca3f"
+ integrity sha512-+bMdgqjMN/Z77a6NlY/I3U5LlRDbnmaAk6lDveAPKwSpcPM4tKAuYsvYF8xjhOPXhOYGe/73vVLVez5PW+jqhw==
-minimist@1.2.0, minimist@^1.1.3:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
- integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=
+minimist@^1.1.3, minimist@^1.2.5:
+ version "1.2.5"
+ resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
+ integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
mixin-deep@^1.2.0:
version "1.3.2"
@@ -1727,12 +1727,17 @@ mixin-deep@^1.2.0:
for-in "^1.0.2"
is-extendable "^1.0.1"
-mkdirp@^0.5.1, mkdirp@~0.5.1:
- version "0.5.1"
- resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
- integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=
+mkdirp@^0.5.1:
+ version "0.5.5"
+ resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
+ integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
dependencies:
- minimist "0.0.8"
+ minimist "^1.2.5"
+
+mkdirp@~1.0.3:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
+ integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
ms@2.0.0:
version "2.0.0"
@@ -1843,9 +1848,9 @@ p-limit@^1.1.0:
p-try "^1.0.0"
p-limit@^2.2.0:
- version "2.2.2"
- resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.2.tgz#61279b67721f5287aa1c13a9a7fbbc48c9291b1e"
- integrity sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
+ integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==
dependencies:
p-try "^2.0.0"
@@ -2117,10 +2122,10 @@ regenerator-runtime@^0.11.0:
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==
-regenerator-runtime@^0.13.2:
- version "0.13.3"
- resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz#7cf6a77d8f5c6f60eb73c5fc1955b2ceb01e6bf5"
- integrity sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==
+regenerator-runtime@^0.13.4:
+ version "0.13.5"
+ resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697"
+ integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==
regex-not@^1.0.0, regex-not@^1.0.2:
version "1.0.2"
@@ -2183,9 +2188,9 @@ resolve-url@^0.2.1:
integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=
resolve@^1.1.6, resolve@^1.10.0:
- version "1.15.1"
- resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.1.tgz#27bdcdeffeaf2d6244b95bb0f9f4b4653451f3e8"
- integrity sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==
+ version "1.17.0"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444"
+ integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==
dependencies:
path-parse "^1.0.6"
@@ -2227,16 +2232,16 @@ rimraf@~2.6.2:
glob "^7.1.3"
run-async@^2.2.0:
- version "2.3.0"
- resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0"
- integrity sha1-A3GrSuC91yDUFm19/aZP96RFpsA=
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.0.tgz#e59054a5b86876cfae07f431d18cbaddc594f1e8"
+ integrity sha512-xJTbh/d7Lm7SBhc1tNvTpeCHaEzoyxPrqNlvSdMfBTYwaY++UJFyXUOxAtsRUXjlqOfj8luNaR9vjCh4KeV+pg==
dependencies:
is-promise "^2.1.0"
rxjs@^6.4.0:
- version "6.5.4"
- resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.4.tgz#e0777fe0d184cec7872df147f303572d414e211c"
- integrity sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==
+ version "6.5.5"
+ resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.5.tgz#c5c884e3094c8cfee31bf27eb87e54ccfc87f9ec"
+ integrity sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ==
dependencies:
tslib "^1.9.0"
@@ -2302,9 +2307,9 @@ shelljs@0.7.6:
rechoir "^0.6.2"
signal-exit@^3.0.0, signal-exit@^3.0.2:
- version "3.0.2"
- resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
- integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"
+ integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==
slash@^1.0.0:
version "1.0.0"
@@ -2376,9 +2381,9 @@ spdx-correct@^3.0.0:
spdx-license-ids "^3.0.0"
spdx-exceptions@^2.1.0:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz#2ea450aee74f2a89bfb94519c07fcd6f41322977"
- integrity sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d"
+ integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==
spdx-expression-parse@^3.0.0:
version "3.0.0"
@@ -2582,9 +2587,9 @@ trim-off-newlines@^1.0.0:
integrity sha1-n5up2e+odkw4dpi8v+sshI8RrbM=
tslib@^1.9.0:
- version "1.11.0"
- resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.0.tgz#f1f3528301621a53220d58373ae510ff747a66bc"
- integrity sha512-BmndXUtiTn/VDDrJzQE7Mm22Ix3PxgLltW9bSNLoeCY31gnG2OPx0QqJnuc9oMIKioYrz487i6K9o4Pdn0j+Kg==
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35"
+ integrity sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==
underscore.string@~3.3.4:
version "3.3.5"
@@ -2668,11 +2673,11 @@ xtend@~4.0.1:
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
yaml@^1.7.2:
- version "1.7.2"
- resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.7.2.tgz#f26aabf738590ab61efaca502358e48dc9f348b2"
- integrity sha512-qXROVp90sb83XtAoqE8bP9RwAkTTZbugRUTm5YeFCBfNRPEp2YzTeqWiz7m5OORHzEvrA/qcGS8hp/E+MMROYw==
+ version "1.9.2"
+ resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.9.2.tgz#f0cfa865f003ab707663e4f04b3956957ea564ed"
+ integrity sha512-HPT7cGGI0DuRcsO51qC1j9O16Dh1mZ2bnXwsi0jrSpsLz0WxOLSLXfkABVl6bZO629py3CU+OMJtpNHDLB97kg==
dependencies:
- "@babel/runtime" "^7.6.3"
+ "@babel/runtime" "^7.9.2"
yargs-parser@^10.0.0:
version "10.1.0"