[Subscription] Subscription Flow
Tags: rails, ruby, webhooks, zoop
Published: 2022-12-20 19:19:08



Neste manual vamos compartilhar como pensamos o nosso motor de Subscriptions dentro da nossa plataforma.

Chamamos de 'manual' e não 'artigo' pois o objetivo deste documento é ser um guia; por isso, a leitura não precisa ser _top down_ e sim ser uma consulta para sanar eventuais dúvidas.

Esperamos que ajude os DEV's da INK e a todos que este documento tocar :)

# Sumário

### Seção 1: Principais diferenças de arquitetura entre o PagarMe e a Zoop
* Bastidores Zoop
* Diferença PagarMe-Zoop: desenho de endpoints
* Diferença PagarMe-Zoop: consumo da API via métodos ao inves de JSON's
* Arquitetura INK para o consumo da API da Zoop

### Seção 2: Novos conceitos de arquitetura gerados pela migração da Zoop
* Motor baseado em Webhooks
* Expiração Manual de Subscriptions


### Seção 3: Atributos essencias de subscription na Zoop e INK  
* due_date
* due_since
* expiration_date
* is_active (loja)
* is_active (subscription)
* status
* trial

### Seção 4: Eventos utilizados
* subscription.created
* subscription.suspended
* subscription.expired
* subscription.active
* invoice.overdue
* invoice.paid

### Seção 5: UseCases utilizados
* Hashie Gem
* Mind Map: Subscription Flow
* UC's: webhooks
* UC's: checkout
* UC's: payment_update

---

## Seção 1: Principais diferenças de arquitetura entre o PagarMe e a Zoop

No início do ano de 2021, demos um grande passo como plataforma: **migramos do sistema de pagamentos do [PagarMe](https://pagar.me/) para a [Zoop](https://zoop.com.br/)**

Sem entrar no mérito dos prós e contras de cada sistema de pagamento, neste artigo vamos focar no desafio técnico e em como a arquitetura de subscriptions foi idealizada.

---
#### Mas antes disso, nos bastidores...
O processo como um todo da migração foi a maior desafio técnico que os Devs da casa tiveram em suas carreiras naquele momento. 

E, como todo o processo onde se adquire experiência, muita 
coisa é feita na base de porrada e quebração de cabeça.

A história completa dos bastidores da migração ainda será um artigo, mas no resumo, alguns pontos relevantes:

* Subdimensionamos o trabalho que seria migrar de um motor de assinaturas para outro
* Na nossa arquitetura antiga, não suspendiamos o serviço de assinatura quando ele expirava, por isso não tinhamos experiência em como fazê-lo _at all_
* Por fim, mais importante, quando o assunto é pagamento muito cuidado com a ânsia de lançar logo sua V0...lançar a migração de pagamento da forma que fizemos, foi bastante arriscado e pouco sustentável em matéria de gestão de conhecimento; isso gerou mais de 2 meses de trabalho posterior ao deploy, entre consertar bugs e readaptar novos motores. Além, é claro, de muita dor de cabeça.

Contudo, cá estamos com um motor novo de assinatura e buscando uma forma de registrar os passos que demos. VQV!

---

#### Voltando...

Tecnicamente, a maior diferença entre o PagarMe e a Zoop, é que o PagarMe é **desenhado para ser implementando com muita velocidade e pouca customização**. 

Na prática, isso gera duas implicações: 

1. **Sistema de pagamento do pagarme chega com os endpoints preparados para a sua aplicação**
2. **O consumo da API se da através de métodos desenhados para a sua aplicação**

Vamos a cada uma delas!

> #### 1-Sistema de pagamento do pagarme chega com os endpoints preparados para a sua aplicação

Por exemplo, para darmos um fetch em uma Subscription pelo PagarMe fazemos:

```
require 'pagarme'

PagarMe.api_key = "SUA_API_KEY"

subscription_id = "ID_DA_ASSINATURA"
subscription = PagarMe::Subscription.find_by_id(subscription_id)
```

Já pela Zoop, precisamos...

a) **Criar uma classe que recebe os endpoints**
b) **Configurar cada um dos tipos de clients, com suas respectivas autenticações**
c)  **No caso da nossa aplicação, criamos uma classe intermediária que modulariza o consumo da API no client side (nosso lado)**
d)  **Por fim, darmos o fetch**

A seguir, cada ponto com exemplos...

--

#### a) Criar uma classe que recebe os endpoints
```
app> service_layers > zoop > plans_and_subscriptions > api > endpoints.rb

class Zoop::PlansAndSubscriptions::Api::Endpoints
  def initialize(client, clientV2)
    @client = client
    @clientV2 = clientV2
  end

...

  def subscription_details(subscription_id:)
    path = "subscriptions/#{subscription_id}"
    data = { subscription_id: subscription_id }
    response = @clientV2.get(path: path, data: data)
    begin
      JSON.parse(response)
    rescue => error

      puts "JSON Parse error => #{error}"
    end
  end
end
```
#### b) Configurar cada um dos tipos de clients, com suas respectivas autenticações

```
app> service_layers > zoop > auth > rest_client_api.rb

class Zoop::Auth::RestClientApi
  def initialize
    @api_key = 'key'
    @mkt_place_id = 'key'
    @api_url = 'key'
  end

  def post(path:,data:)
    url = url(path)
    Rails.logger.info data

    request = RestClient::Request.new(method: :post, url: url,payload: data, user: @api_key)
    response = request.execute

    response
  end
...
end
```

#### c)No caso da nossa aplicação, criamos uma classe intermediária que modulariza o consumo da API no client side (nosso lado):

```
app> service_layers > zoop > plans_and_subscriptions > subscriptions > details.rb

class Zoop::PlansAndSubscriptions::Subscriptions::Details
    def initialize(subscription_id:)
        @subscription_id = subscription_id
        @endpoints = Zoop::PlansAndSubscriptions::Api::Endpoints.new(Zoop::Auth::RestClientApi.new, Zoop::Auth::RestClientApiV2.new)
    end
  
    def execute
        subscription_details = @endpoints.subscription_details(subscription_id: @subscription_id)
    end   
end
```

#### d)Por fim, darmos o fetch

```
subscription_details = Zoop::PlansAndSubscriptions::Subscriptions::Details.new(
subscription_id: @subscription.zoop_subscription_id).execute
```
--

> #### 2-O consumo da API se da através de métodos desenhados para a sua aplicação

No pagarMe, para você acessar o fim do período de uma subscription (atributo `current_period_end`) é feito através de:

```
irb(main):003:0> PagarMe::Subscription.all.last.current_period_end
RestClient.get "https://api.pagar.me/1/subscriptions", "{\"page\":1,\"count\":10}", "Accept"=>"application/json", "Content-Length"=>"21", "Content-Type"=>"application/json; charset=utf8", "User-Agent"=>"pagarme-ruby/2.4.0", "X-PagarMe-User-Agent"=>"pagarme-ruby/2.4.0"
# => 200 OK | application/json 19666 bytes, 0.15s
=> "2021-03-10T00:27:31.849Z"
irb(main):004:0> 

```

Já na Zoop, consumindo a API você recebe JSON's. Com isso, para acessar o fim do período de uma subscription (atributo `expiration_date`) fazemos:

```
irb(main):004:0> subscription_details = Zoop::PlansAndSubscriptions::Subscriptions::Details.new(
irb(main):005:1* subscription_id: Subscription.all.last.zoop_subscription_id).execute
  Subscription Load (2.2ms)  SELECT "subscriptions".* FROM "subscriptions" WHERE "subscriptions"."deleted_at" IS NULL ORDER BY "subscriptions"."id" DESC LIMIT $1  [["LIMIT", 1]]
=> {"id"=>"33ea0db33dc3430489527a6f1f2c1b6d", "marketplace_id"=>"aa671febc9d4466b9f34134327c56d20", "plan"=>"561ce6726be74b16b9fe4c51f6a94b03", "currency"=>"BRL", "updated_at"=>"2021-08-02T23:18:30+00:00", "tolerance_period"=>nil, "on_behalf_of"=>"36fabbcd71a540a2b4a34ae338b58069", "payment_method"=>"credit", "due_since"=>nil, "expiration_date"=>"2021-08-16T23:00:00", "created_at"=>"2021-08-02T22:45:53+00:00", "suspended_at"=>"2021-08-02T23:18:30+00:00", "due_date"=>"2021-08-16", "status"=>"suspended", "amount"=>9900, "customer"=>"10066c2410f9427ea3d6553b8dc8baeb"}
irb(main):006:0> subscription_details["expiration_date"]
=> "2021-08-16T23:00:00"
irb(main):007:0> 
```

Essas duas diferenças já mudam drasticamente a maneira como todas as entidades de assinaturas vão se relacionar entre API-INK. 

Contudo, a principal questão para o nosso time foi que não pensamos nos processos de interface da API com a nossa aplicação, como
* renovação de pagamento 
* expiração de subscription
* exclusão de multiplas subscriptions em caso de upsell
* etc 

pois no PagarMe, esses processos eram realizados automaticamente no lado do PagarMe.

Com isso, vamos aos principais conceitos de arquitetura trazidos pela escolha da Zoop

---

## Seção 2: Novos conceitos gerados pela migração da Zoop

#### Motor baseado em Webhooks

Pela Zoop, recomenda-se que usemos os chamados Webhooks para "ouvir" as interações entre a API e a INK. 

Na prática, um Webhook é de fato um "gancho" para que quando uma ação ocorra na API, haja uma chamada para o _client side_ informando a ação e o que foi alterado.

Por exemplo, toda vez que houver uma criação de subscription na Zoop, haverá um trigger no evento `subscription.created`; com isso, podemos criar um Webhook que, toda vez que houver um `subscription.created`, possamos usar informações deste evento para atualizar nosso banco e aplicar nossas próprias regras de negócio.

--

#### Expiração Manual de Subscriptions

Na Zoop, a expiração de uma subscription é feita manualmente. 

O que isso significa?

> Simples, nós que precisamos dizer quando uma assinatura expira

Na prática isso trouxe uma grande dor de cabeça:

**Como vamos setar e atualizar o campo de expiração para que o fluxo de renovação e suspensão de assinatura funcione corretamente?**

_A resposta à esta pergunta virá na explicação dos UseCases utilizados na nossa aplicação_

---
## Seção 3: Atributos essencias de subscription na Zoop e INK 

Nesta seção, nosso objetivo é explicar cada um dos principais atributos gerados pela subscription na zoop e na nossa base.

#### Atributos Zoop
* `due_date`: Data da próxima cobrança para a Subscription
* `expiration_date`: Data de expiração para a Subscription
* `due_since`: Data do último pagamento atribuido a subscription
* `status`: Status que se encontra a Subscription (_active_, _suspended_, _expired_)

#### Atributos INK
* `expiration_date`: Reflexo direto do expiration_date da Zoop
* `is_active (store)`: Booleano que indica se a loja está ativa ou não; caso não esteja, o usuário não conseguirá realizar vendas em sua loja. 
* `is_active (subscription)`: Booleano que indica se a subscripton está ativa ou não; caso o usuário não possua nenhuma subscription ativa (`is_active = true`), a loja terá, em tese, um `is_active = false`
* `trial`: Booleano que diz se a condição de trial para subscription é `true` ou `false`
* `status`: Reflexo direto do `status` vindo da Zoop

A interface entre os atributos ficará clara na seção de **UseCases Utilizados**

---

## Seção 4: Eventos utilizados

Nesta seção, vamos explicitar cada um dos eventos utlizados pela nossa aplicação, um payload de exemplo e quando idealizamos que eles seriam triggados

#### subscription.created
* Payload
```
 {
            "created_at": "2021-08-04T12:05:30+00:00",
            "payload": {
                "marketplace_id": "aa671febc9d4466b9f34134327c56d20",
                "suspended_at": null,
                "payment_method": "credit",
                "currency": "BRL",
                "expiration_date": null,
                "due_since": null,
                "amount": 49900,
                "updated_at": "2021-08-04T12:05:30+00:00",
                "created_at": "2021-08-04T12:05:30+00:00",
                "status": "active",
                "id": "b66d91d2963445ef81aab5f173551b21",
                "due_date": "2021-08-18",
                "on_behalf_of": "36fabbcd71a540a2b4a34ae338b58069",
                "plan": "561ce6726be74b16b9fe4c51f6a94b03",
                "tolerance_period": null,
                "customer": "25b1a9c31ebc43fb85975eed7433761e"
            },
            "resource": "event",
            "status": "succeeded",
            "uri": "/v1/marketplaces/aa671febc9d4466b9f34134327c56d20/events/5a55ea1828bc409087f4b6f02bdef9f3",
            "id": "5a55ea1828bc409087f4b6f02bdef9f3",
            "dispatches": [
                {
                    "created_at": "2021-08-04T12:05:40+00:00",
                    "status": "succeeded",
                    "replay": false,
                    "webhook_id": "59c61025a6384edd8295493a4d0f55f5"
                }
            ],
            "type": "subscription.created"
        }
```

* Trigger

No checkout de subscription (`app>app_core>subscription>use_cases>checkout>process_subscription_checkout.rb`), há a criação da subscription da Zoop e neste momento imaginamos que este evento será triggado.

#### subscription.suspended

* Payload
```
 {
            "created_at": "2021-08-04T12:15:18+00:00",
            "payload": {
                "payment_method": "credit",
                "currency": "BRL",
                "tolerance_period": null,
                "updated_at": "2021-08-04T12:15:18+00:00",
                "expiration_date": "2021-08-18T23:00:00",
                "created_at": "2021-08-04T12:05:30+00:00",
                "customer": "25b1a9c31ebc43fb85975eed7433761e",
                "on_behalf_of": "36fabbcd71a540a2b4a34ae338b58069",
                "marketplace_id": "aa671febc9d4466b9f34134327c56d20",
                "amount": 49900,
                "suspended_at": "2021-08-04T12:15:18+00:00",
                "status": "suspended",
                "plan": "561ce6726be74b16b9fe4c51f6a94b03",
                "due_date": "2021-08-18",
                "id": "b66d91d2963445ef81aab5f173551b21",
                "due_since": null
            },
            "resource": "event",
            "status": "succeeded",
            "uri": "/v1/marketplaces/aa671febc9d4466b9f34134327c56d20/events/c1a933089175476d86e902a003b3b01e",
            "id": "c1a933089175476d86e902a003b3b01e",
            "dispatches": [
                {
                    "created_at": "2021-08-04T12:15:28+00:00",
                    "status": "succeeded",
                    "replay": false,
                    "webhook_id": "1c10b9ecf9c94687a43d7946d35f6141"
                }
            ],
            "type": "subscription.suspended"
        }
```

* Trigger

Quando o usuário acessa a rota de /user/subscription, caso sua subscription esteja ativa, irá aparecer um botão escrito "Suspender Assinatura". No click deste botão é suspensa a assinatura e é trigado este evento (`app>app_core>subscription>use_cases>webhooks>handle_subscription_suspended_event.rb`)

#### subscription.expired
* Payload
```
 {
            "created_at": "2021-08-04T12:28:01+00:00",
            "payload": {
                "payment_method": "credit",
                "on_behalf_of": "36fabbcd71a540a2b4a34ae338b58069",
                "created_at": "2021-04-19T19:26:51+00:00",
                "suspended_at": null,
                "tolerance_period": null,
                "expiration_date": "2021-06-10T09:01:54",
                "amount": 12900,
                "updated_at": "2021-08-04T12:28:01+00:00",
                "id": "67c9583d6b1f408ca2bbad23677901d6",
                "plan": "561ce6726be74b16b9fe4c51f6a94b03",
                "due_date": "2021-05-03",
                "customer": "f4f34cd05e2743029cab8e47a1e03e35",
                "status": "expired",
                "due_since": null,
                "currency": "BRL",
                "marketplace_id": "aa671febc9d4466b9f34134327c56d20"
            },
            "resource": "event",
            "status": "succeeded",
            "uri": "/v1/marketplaces/aa671febc9d4466b9f34134327c56d20/events/12e32795b0ce49dc8a914e36723514f3",
            "id": "12e32795b0ce49dc8a914e36723514f3",
            "dispatches": [
                {
                    "created_at": "2021-08-04T12:28:11+00:00",
                    "status": "succeeded",
                    "replay": false,
                    "webhook_id": "5388ead647cb4e9d93c78f33797e90ff"
                }
            ],
            "type": "subscription.expired"
        }
```

* Trigger

Este evento será triggado quando a subscription chegar na sua data de expiração e não houver renovação da assinatura. Isso acontece quando o usuário não pagou a subscription e, no dia de expiração, a assinatura irá de _active_ para _expired_

#### subscription.active
* Payload
```
  {
            "created_at": "2021-08-04T12:58:40+00:00",
            "payload": {
                "payment_method": "credit",
                "currency": "BRL",
                "tolerance_period": null,
                "updated_at": "2021-08-04T12:58:40+00:00",
                "expiration_date": "2021-09-03T23:59:59",
                "created_at": "2021-06-16T13:52:43+00:00",
                "customer": "74369907fd1f446d9777c201be73eef6",
                "on_behalf_of": "36fabbcd71a540a2b4a34ae338b58069",
                "marketplace_id": "aa671febc9d4466b9f34134327c56d20",
                "amount": 12900,
                "suspended_at": null,
                "status": "active",
                "plan": "561ce6726be74b16b9fe4c51f6a94b03",
                "due_date": "2021-07-30",
                "id": "dc9ace773b63461c81139bd00d33c9d0",
                "due_since": "2021-06-30"
            },
            "resource": "event",
            "status": "succeeded",
            "uri": "/v1/marketplaces/aa671febc9d4466b9f34134327c56d20/events/b71e8a87ff0d41798be9033118342c9f",
            "id": "b71e8a87ff0d41798be9033118342c9f",
            "dispatches": [
                {
                    "created_at": "2021-08-04T12:58:50+00:00",
                    "status": "succeeded",
                    "replay": false,
                    "webhook_id": "3286bf41cbf2474d87e29e2331d0fef5"
                }
            ],
            "type": "subscription.active"
        }
```

* Trigger

Este evento terá trigger quando uma assinatura for suspensa e posteriormente for reativada. Isso pode ser feito através do /user/subscription; quando o usuário suspender a assinatura, aparecerá uma opção "Reativar Assinatura". O click deste botão irá reativar a assinatura e triggar este evento

#### invoice.overdue
* Payload
```
  {
            "resource": "event",
            "created_at": "2021-08-04T13:02:13+00:00",
            "uri": "/v1/marketplaces/aa671febc9d4466b9f34134327c56d20/events/36a24c2ab51746e4887ab5b3d318f32f",
            "status": "succeeded",
            "id": "36a24c2ab51746e4887ab5b3d318f32f",
            "dispatches": [
                {
                    "created_at": "2021-08-04T13:02:23+00:00",
                    "webhook_id": "b23ea12e177c482083b4ea02dae54f7d",
                    "status": "succeeded",
                    "replay": false
                }
            ],
            "type": "invoice.overdue",
            "payload": {
                "id": "56fd660c44c240afb0a3869c6398ab40",
                "setup_amount": null,
                "tolerance_period": null,
                "transactions": [],
                "description": null,
                "on_behalf_of": "36fabbcd71a540a2b4a34ae338b58069",
                "amount": 12900,
                "voided_at": null,
                "due_date": "2021-07-30T00:00:00",
                "status": "failed",
                "retries": 3,
                "payment_method": "credit",
                "paid_at": null,
                "subscription": "dc9ace773b63461c81139bd00d33c9d0",
                "expiration_date": null,
                "resource": "invoice",
                "invoice_customer": {
                    "first_name": "",
                    "last_name": null,
                    "taxpayer_id": null,
                    "id": "74369907fd1f446d9777c201be73eef6",
                    "email": "laurormn@gmail.com"
                },
                "max_retries": 3
            }
        }
```

* Trigger

Primeiramente, um "invoice" é uma fatura. O conceito de fatura geralmente está relacionado com recorrência, quase como se fosse uma "conta" que chega periodicamente para você. 

Na nossa aplicação, a única "conta" que chega recorrentemente para os usuários é a subscription. Logo, todos os eventos de invoice, hoje, estão relacionados a subscription.

O `invoice.overdue` significa que aquela fatura teve o seu número máximo de tentativas de pagamento atingido. Ou seja, o cartão de crédito associado pode estar sem limite disponível, pode não estar mais válido (caso de cartão de crédito virtual que é excluido) entre outras possibilidades. O fato é que o pagamento não aconteceu. 

Este evento acontece no `due_date` (data da pŕoxima cobrança) da assinatura. Se no dia da cobrança ela for mal sucedida, este evento será trigado. 

#### invoice.paid
* Payload
```
{
            "resource": "event",
            "created_at": "2021-08-04T13:03:13+00:00",
            "uri": "/v1/marketplaces/aa671febc9d4466b9f34134327c56d20/events/7dbdc97c63df45f8bd40e80595b9e7a7",
            "status": "succeeded",
            "id": "7dbdc97c63df45f8bd40e80595b9e7a7",
            "dispatches": [
                {
                    "created_at": "2021-08-04T13:03:23+00:00",
                    "webhook_id": "9d453bca7b9947d4987d5e02dec0c692",
                    "status": "succeeded",
                    "replay": false
                }
            ],
            "type": "invoice.paid",
            "payload": {
                "payment_method": "credit",
                "status": "paid",
                "invoice_customer": {
                    "id": "888e1165cddf44d0a8eab133f41f53e5",
                    "taxpayer_id": "34498991000104",
                    "email": "suporte@arquiteturaequestre.com.br",
                    "first_name": "",
                    "last_name": null
                },
                "amount": 12900,
                "voided_at": null,
                "description": null,
                "due_date": "2021-08-04T00:00:00",
                "setup_amount": null,
                "retries": 0,
                "tolerance_period": null,
                "resource": "invoice",
                "paid_at": "2021-08-04T13:03:13+00:00",
                "max_retries": 3,
                "transactions": [
                    {
                        "masked_card": "5226***2794",
                        "card_brand": "MasterCard",
                        "currency": "BRL",
                        "id": "f00d64630d3d4af881a145371775f6b2"
                    }
                ],
                "on_behalf_of": "36fabbcd71a540a2b4a34ae338b58069",
                "id": "35c9881a40654d1d87b1d2e0fe8251b4",
                "subscription": "6bf2e484c5a249a98ca3e12196c5c41c",
                "expiration_date": null
            }
        }
```

* Trigger

Como já explicado no evento anterior, um `invoice` é uma fatura. O evento `ìnvoice.paid` diz que uma fatura foi paga. Inclusive neste evento podemos saber a `transaction_id` relacionada a subscription.

---
## Seção 5: UseCases utilizados

Antes de começarmos a falar sobre os UC, precisamos falar de dois pontos essenciais:

1. Hashie Gem
2. Como projetamos cada UC

#### 1-[Hashie Gem](https://github.com/hashie/hashie)
Esta é uma Gem que usamos no Flow de Subscription que facilitou muito o nosso lado na hora de manipular os `payloads` chegados pela Zoop através dos Webhooks

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mvn5pk1ycfgx1gf9n84c.png)

Para resumir a história do uso por de trás da Gem, vamos falar apenas sobre a _dor resolvida_ e como _implementamos ela no projeto_

* Dor resolvida
Imagina que, recebendo um `hash` do evento `invoice.paid`, por exemplo, queiramos acessar a `transaction_id` desta fatura.

Para isso, quando chega o payload de exemplo:
```
 {
            "resource": "event",
            "created_at": "2021-08-04T14:42:13+00:00",
            "uri": "/v1/marketplaces/aa671febc9d4466b9f34134327c56d20/events/190dc7e15d414005aafe90d028ec54c3",
            "status": "succeeded",
            "id": "190dc7e15d414005aafe90d028ec54c3",
            "dispatches": [
                {
                    "created_at": "2021-08-04T14:42:23+00:00",
                    "webhook_id": "9d453bca7b9947d4987d5e02dec0c692",
                    "status": "succeeded",
                    "replay": false
                }
            ],
            "type": "invoice.paid",
            "payload": {
                "payment_method": "credit",
                "status": "paid",
                "invoice_customer": {
                    "id": "a6080bb9b36c4af8a9c9fce651fd7ea8",
                    "taxpayer_id": "39123487828",
                    "email": "weynerenan@gmail.com",
                    "first_name": "Renan",
                    "last_name": "Weyne"
                },
                "amount": 12900,
                "voided_at": null,
                "description": "Fatura avulsa de assinatura para loja 3w",
                "due_date": "2021-08-04T00:00:00",
                "setup_amount": null,
                "retries": 0,
                "tolerance_period": null,
                "resource": "invoice",
                "paid_at": "2021-08-04T14:42:13+00:00",
                "max_retries": 3,
                "transactions": [
                    {
                        "masked_card": "5502***5987",
                        "card_brand": "MasterCard",
                        "currency": "BRL",
                        "id": "dcab432a48e445e3b5c42a0e91400bdb"
                    }
                ],
                "on_behalf_of": "36fabbcd71a540a2b4a34ae338b58069",
                "id": "f304c7eb4d8d464b96e94e44a33a1e35",
                "subscription": "5c99dfbb6dd745db9e75159f9b8912e9",
                "expiration_date": null
            }
        }
```
Podemos colocar este numa variável `event` e extrair o `transaction_id` através de:

```
event["payload"]["transactions"][0]["id"]
```

O Hashie chega para prevenir esta sintax! Com ele, podemos buscar o chave de um `hash` através de métodos intuitivos e que facilitam muito o manejo de objetos com estrutura em `hash`.

No exemplo do payload, com o Hashie, poderíamos extrair o `transaction_id` fazendo:

```
@event_params = event_params
@transactions = (@event_params.deep_find("transactions"))[0]
@transactions["id"]
```

E por isso usamos o Hashie! Para facilitar a sintax de manipulação de objetos com estrutura em `hash` :)

* Implementação no Projeto

A implementação aconteceu em 3 etapas

a) Adicionar no projeto os módulos associados ao Hashie.
`app>services>hashie>deep_find.rb`
`app>services>hashie>deep_locate.rb`
`app>services>hashie>deep_fetch.rb`

b) Criamos um Presenter* que recebe o `hash` como `params` na chamada da nossa aplicação pelo webhook da Zoop chamado `ParametersPresenter`

```
class ParametersPresenter < BasePresenter
  def initialize(event_params:)
    @event_params = event_params
  end

  def build
    @event_params.permit! rescue nil
    hash_params = @event_params.to_h rescue @event_params

    hash_params.extend Hashie::Extensions::DeepFind
    hash_params.extend Hashie::Extensions::DeepFetch
    hash_params.extend Hashie::Extensions::DeepLocate
  end
end
```

Este `Presenter` tem como finalidade adaptar um objeto que chega como `ActionController::Paramaters` para um objeto `Hash` com métodos do `Hashie` como o `deep_find` mostrado anteriormente.

c) Damos um build em cada UC para transformar um `ActionController::Parameters` um `Hash` com PowerUps da gem do Hashie

Exemplo:


```
module Subscription::UseCases::Webhooks
  class HandleInvoicePaidEvent
    def self.build(event_params:)
      new(
        event_params: ParametersPresenter.new(event_params: event_params).build
      )
    end
```

Pronto! Agora já sabemos como usar o `Hashie` :)

#### 2-[Como projetamos cada UC](https://miro.com/app/board/o9J_lGnhzCo=/)

Para este projeto, estreiamos o conceito de Mind Map na INK.
 
O objetivo era ter visualmente um fluxo de como cada ação do usuário refletia um evento na Zoop e, consequentemente, um script na nossa aplicação.

Este Mind Map foi essencial para estruturarmos a interface de cada atributo da INK com a Zoop e pensarmos na execução de cada UC

Hora dos UC's...

#### use_cases>webhooks

* handle_invoice_paid_event.rb

Abaixo cada um dos métodos que são executados no evento `invoice.paid`

```
app>app_core>subscription>use_cases>webhooks>handle_invoice_paid.event.rb

    def execute
      set_active_status_into_db
      reactivate_status_into_zoop
      set_store_is_active
      set_subscription_is_active
      set_db_expiration_date
      set_zoop_expiration_date
      remove_trial
      create_subscription_invoice_record
    end
```
Interfaces:
a) Atributo `status` sendo setado `active` na Zoop e na INK
b) Atributo `is_active` da loja sendo setado para `true`
c) Atributo `is_active` da subscription sendo setado para `true`
d) Atributo `trial` sendo setado para `false` pois houve pagamento
e) Criação de um `SubscriptionInvoice` para acessarmos o invoice na nossa base com mais facilidade.
f) Atributo `expiration_date` sendo atualizado na Zoop e na INK. Ambos serão atualizados para o dia de hoje + dias do plano (anual, mensal); o final do dia que resultará esta operação, será o novo `expiration_date`

```
    def set_zoop_expiration_date
      current_expiration_date       = @subscription.expiration_date
      plan_days                     = @subscription.plan.interval

      Subscription::UpdateExpirationDateJob.perform_later(
        subscription: @subscription,
        expiration_date: plan_days.days.from_now.end_of_day.strftime('%Y-%m-%dT%H:%M:%S')
      )
    end
```

_PS: A escolha do final do dia foi proposital. Isso ocorre pois o due_date (próxima cobrança) irá acontecer no início do dia, então caso não haja pagamento ao longo do dia, no final do dia haverá expiração_

* handle_invoice_overdue.rb

Abaixo cada um dos métodos que são executados no evento `invoice.overdue`

```
      def execute
        notify_invalid_credit_card_to_user
      end
```

Interfaces
a) Não há interfaces de atributos; aqui apenas notificamos o usuário do cartão de crédito inválido.

* handle_subscription_active_event.rb

Abaixo cada um dos métodos que são executados no evento `subscription.active`

```
    def execute
      set_status
    end
```

Interface
a) Atualizamos o atributo `status` na nossa base para `active`.

* handle_subscription_created_event.rb

Abaixo cada um dos métodos que são executados no evento `subscription.created`

```
    def execute
      set_trial
      set_status
      set_db_expiration_date
      set_zoop_expiration_date
    end
```

Interfaces
a) Setamos o atributo `trial` para `true` dado que a subscription acabou de ser criada
b) Setamos o atributo `status` para `active` 
c) Setamos o atributo `expiration_date` para o `due_date` da subscription, no final do dia.

_PS: A escolha do final do dia foi proposital. Isso ocorre pois o due_date (próxima cobrança) irá acontecer no início do dia, então caso não haja pagamento ao longo do dia, no final do dia haverá expiração_


  
* handle_subscription_expired_event.rb

Abaixo cada um dos métodos que são executados no evento `subscription.expired`

```
    def execute
      set_status
      set_subscription_is_active
      handle_store_is_active
    end
```

Interfaces
a) Atributo `status` setado para `expired`
b) Atributo `is_active` da subscription é setado para `false`
c) Caso não hajam subscriptions com status `active` para aquele usuário, sua loja será desativada.
 

* handle_subscription_suspended_event.rb

Abaixo cada um dos métodos que são executados no evento `subscription.suspended`

```
    def execute
      set_status
      handle_store_block
    end
```

Interfaces:
a) Setamos o atributo `status` para `suspended`
b) Lançamos um `BackgroundJob` para acontecer no dia da expiração da subscription; quando este BJ rodar, caso não hajam subscription com `is_active=true`, a loja será desativada.

Isso precisou ser feito pois as subscriptions `suspended` não migram para `expired` no dia da expiração; ou seja, elas nunca terão evento `subscription.expired` e precisamos usar este BJ para assegurar a desativação.

Exemplo:

```
        {
            "payment_method": "credit",
            "currency": "BRL",
            "tolerance_period": null,
            "updated_at": "2021-06-10T11:56:04+00:00",
            "expiration_date": "2021-06-10T08:56:02",
            "created_at": "2021-02-25T13:21:57+00:00",
            "customer": "a0ddaadd8c474a3193dcb153d88e0ea2",
            "on_behalf_of": "36fabbcd71a540a2b4a34ae338b58069",
            "marketplace_id": "aa671febc9d4466b9f34134327c56d20",
            "amount": 12900,
            "suspended_at": "2021-05-17T19:36:34+00:00",
            "status": "suspended",
            "plan": "561ce6726be74b16b9fe4c51f6a94b03",
            "due_date": "2021-06-11",
            "id": "3ced54f621d340bda03e7b39ceb34caf",
            "due_since": "2021-03-11"
        }
```

Subscription com `expiration_date` para `2021-06-10T08:56:02` (data já ocorrida) e mesmo assim com status `suspended`; mais uma vez, subscriptions `suspended` não triggam `subscription.expired`


#### Checkout
* process_subscription_checkout.rb

```
        def execute
            create_buyer_id
            suspend_old_subscription
            create_zoop_subscription
            associate_credit_card_to_customer
            create_subscription_database_subscription
            create_store
        end
```

#### Payment Update
* handle_subscription_reactivation_process.rb

```
        def execute
            associate_new_credit_card_to_zoop_customer
            associate_new_credit_card_token_to_subscription
            create_single_invoice
        end
```

Aqui, para reativarmos uma loja que está desativada, há um checkout especial (https://reserva.ink/subscriptions/reactivation/checkout).

Nele, simplesmente associamos um novo cartão de crédito ao usuário e criamos uma fatura avulsa para ser paga instanteamente. 

Quando ela for paga, o evento `invoice.paid` será triggado e a loja será reativada.

* handle_credit_card_change_process.rb

```
        def execute
            associate_new_credit_card_to_zoop_customer
            associate_new_credit_card_token_to_subscription
        end
```

---