Add aweber roam files

This commit is contained in:
Correl Roush 2021-09-01 16:57:39 -04:00
parent 23301f89d1
commit 9775046d26
90 changed files with 7001 additions and 0 deletions

View file

@ -0,0 +1,14 @@
:PROPERTIES:
:ID: ac416861-ce45-49ac-8b60-f8ea39362135
:END:
#+title: Migration to common RabbitMQ
All services and consumers pointed at the legacy RabbitMQ cluster in
Conshohocken should be migrated to the new common-rabbitmq cluster.
The new servers are available at
=common-rabbitmq.service.${ENVIRONMENT}.consul=.
* Legacy hostnames
- =rabbitmq.service.${ENVIRONMENT}.consul=
- =rabbit{1-3}.int.{stg,prd}.csh=

View file

@ -0,0 +1,4 @@
:PROPERTIES:
:ID: e4d00c11-da8a-4c91-8f38-ce939846e5cb
:END:
#+title: CoreAPI

View file

@ -0,0 +1,10 @@
:PROPERTIES:
:ID: ddeea682-c8f0-4607-8e2b-0f8ee4fd6191
:END:
#+title: Puppet
- Repository :: [[https://gitlab.aweber.io/PSE/config-management/puppet/]]
* Applying changes on a node
- Ensure changes are merged and tagged in the upstream repository.
- As root, run =puppetd --test=.

View file

@ -0,0 +1,4 @@
:PROPERTIES:
:ID: 592aa825-154c-4659-8193-75b0ce1f2e5c
:END:
#+title: PGBouncer port migration

View file

@ -0,0 +1,7 @@
:PROPERTIES:
:ID: ebea379a-8fa6-4e22-9275-a9fc98c02804
:END:
#+title: Pagerduty
https://aweber.pagerduty.com/

View file

@ -0,0 +1,8 @@
:PROPERTIES:
:ID: 24578fe5-6ca0-4000-a7cd-201e952e4c76
:END:
#+title: Mail Relay
A postfix instance running in Kubernetes for the express purpose of supporting
legacy emails in the [[id:57ee2f00-9bcd-4e0f-8a77-ae1f2d4cda89][Control Panel]] until they are all migrated to use [[id:32c66bc8-a397-4f50-96cd-2aec70dd14c5][Corporate
Notifications]].

View file

@ -0,0 +1,4 @@
:PROPERTIES:
:ID: e1b95d0e-366e-4ecf-b867-409b6b6c6ee8
:END:
#+title: Momentum

View file

@ -0,0 +1,4 @@
:PROPERTIES:
:ID: 57ee2f00-9bcd-4e0f-8a77-ae1f2d4cda89
:END:
#+title: Control Panel

View file

@ -0,0 +1,4 @@
:PROPERTIES:
:ID: 32c66bc8-a397-4f50-96cd-2aec70dd14c5
:END:
#+title: Corporate Notifications

View file

@ -0,0 +1,11 @@
:PROPERTIES:
:ID: 7a362881-875f-4f74-8053-55f63826da63
:END:
#+title: Refunding an Order
Refund options (via Admin)
1. Use a balance item
1. Insert a manual balance item, set Term at 1, a negative amount, and optionally a description.
2. Add an invoice and pay it.
2. Close the current package
1. Open the current package, select cancellation in date closed, with a cancel reason

View file

@ -0,0 +1,46 @@
:PROPERTIES:
:ID: d17e934b-b340-4246-88f0-9b36527100c0
:END:
#+title: Login Throttling
* CAPTCHA Throttling
We have login captcha throttling in place for the following:
| Tracked behavior | CAPTCHA threshold | Time Interval |
|-------------------------------------------------------+-------------------+---------------|
| Repeated unsuccessful attempts with the same username | 3 attempts | 10 minutes |
| Repeated attempts from the same IP address | 3 attempts | 12 hours |
| Repeated attempts using the same Sift ID | 3 attempts | 30 minutes |
| Invalid or missing CSRF token | Immediate | N/A |
| Missing customer cookie | Immediate | N/A |
When a user meets one of the thresholds above, they will be presented with a
CAPTCHA challenge. This does not necessarily mean a puzzle will have to be
solved, only that the CAPTCHA script will attempt to determine if the user is a
bot. Even if the user has correctly entered their credentials on the subsequent
attempt, the CAPTCHA challenge will still occur.
All of the above thresholds are checked concurrently for each login attempt.
When a throttled user logs in successfully, the following occurs, the *username*
threshold is reset. No other thresholds are cleared. This means that even after
a user is able to successfully log in to an account, it is still possible for
them to be throttled after failing to log in again because they are now being
throttled by IP address.
* Sift ID Blocking
During previous login attacks, we've documented a set of Sift IDs that have been
used repeatedly during those attempts. Those IDs are blocked with CAPTCHA
*immediately*, with a 20% chance that we will present them a faked successful
response. This is done to throw off attackers using these IDs.
* Code
All the captcha / throttling logic thats currently in place lives in
[[https://gitlab.aweber.io/CP/applications/sites/-/blob/master/aweber_app/controllers/account_controller.php][aweber_app/controllers/account_controller.php]], mainly in the =loginAjax= and
=isThrottled= methods. The repeated actions are tracked using
[[https://gitlab.aweber.io/CP/applications/sites/-/blob/master/php5-vendors/vendors/throttler.php][php5-vendors/vendors/throttler.php]], which uses counters in Redis with a TTL
attached.
* Graphs
Login attempts and throttling are graphed in Grafana on the [[https://grafana.aweber.io/d/000000530/account-logins][Account Logins
dashboard]].

View file

@ -0,0 +1,70 @@
:PROPERTIES:
:ID: a81b2ff0-5ede-44b3-8f82-960357f15428
:END:
#+title: Python Services
#+OPTIONS: ^:nil
#+PROPERTY: header-args :exports code
* Platform
- Python 3.9[fn:programming-languages]
- Tornado 6[fn:frameworks]
* Code Style
Code style must be enforced using flake8.[fn:python-lint-checking]
#+begin_example
[flake8]
application-import-names = PACKAGE_NAME,tests
exclude = build,env
import-order-style = pycharm
#+end_example
* Requirements
** Uncaught errors are logged and alerted via Sentry
#+NAME: packages
- =sentry-sdk=
#+begin_src python
from sentry_sdk import init
init(SENTRY_DSN)
#+end_src
** A status endpoint is exposed
The endpoint should be provide the following fields:
- application :: The name of the service
- environment :: The operating environment the instance of the service is
running in (i.e. "development", "testing", "staging" or "production")
- status :: Current service status (e.g.: "ok", "starting")
- version :: Packaged version of the service instance
- python_version :: The python version running the service instance
(=platform.python_version()=)
The endpoint should return =200= when the service is healthy, and =503= if the
service is not ready to serve requests.
** The service self-hosts its API documentation
An OpenAPI specification is hosted using ReDoc at the root service URL.[fn:backend-services]
** Test coverage reports are available in SonarQube
** Structured logging
json-scribe
** Provides consistent error responses
json-problem
** The service represents itself using its service name and version
- The service must include its name and version in its =Server= response header.[fn:response-headers]
- The service must include its name and version in the =User-Agent= header for all its HTTP requests.[fn:user-agent]
Both of these should be presented as =${service-name}/${version}=, e.g.:
=user-management/1.0.0=.
* References
- [[https://confluence.aweber.io/display/STD/Back+End+Services]]
* Footnotes
[fn:user-agent] https://confluence.aweber.io/display/STD/RESTful+APIs#heading-User-AgentRequestHeader
[fn:response-headers] https://confluence.aweber.io/display/STD/RESTful+APIs#heading-ResponseHeaders
[fn:backend-services] https://confluence.aweber.io/display/STD/Back+End+Services
[fn:frameworks] https://confluence.aweber.io/display/STD/Development+Frameworks
[fn:python-lint-checking] https://confluence.aweber.io/display/STD/Python+Lint+Checking
[fn:programming-languages] https://confluence.aweber.io/display/STD/Programming+Languages

View file

@ -0,0 +1,58 @@
:PROPERTIES:
:ID: dcb2f0ad-72e8-41ff-84f5-07caf8c7fe8e
:END:
#+title: Easy Commerce MVP Brainstorm Notes
#+TODO: ASK(a) FOLLOW-UP(f) | ANSWERED(d)
#+DATE: <2020-10-29 Thu>
* ANSWERED How is a product identified and tracked?
- The customer configures a product name and price on the landing page.
- The product name is the "goal description" (page description or note in the
DB) in our current sales tracking.
- We'd like to include the product name, price, and URL in the subscriber's
activity.
- Full, separate product tracking could be something we build later if there is
sufficient interest.
* ANSWERED How will the Stripe integration be configured?
Will it be configured at the account level, and if so, could it leverage the
existing system for linking things like FB & Twitter?
----------------------------------------------------------------------
- Stripe links via OAuth.
- We will have only one Stripe account per AWeber account.
- We will be logging charges to Stripe, not full orders (with product tracking
information).
- Investigation on whether the existing system will work is pending.
* ANSWERED How does this interact with service limits?
- Availability of the easy commerce feature
- Subscriber limits?
----------------------------------------------------------------------
- Handle errors with free-tier subscriber limits.
- Consider transaction service fee changes based on service limits.
* ANSWERED When should the buyer be added as a subscriber?
Immediately upon payment submission, or upon asynchronous payment confirmation?
----------------------------------------------------------------------
- The async web hook confirms the payment was made successfully.
+ We're avoiding putting logic here only to limit the scope of adding storage
for subscriber and purchase information to react to the event with.
- If the subscriber already exists, we'll update them with any new information.
* ANSWERED How will reports be handled?
Is there a dependency on the upcoming Analytics View service?
----------------------------------------------------------------------
- Only add All Lists: Products Sold in MVP
+ This seems like it'd just be a filter on event type in our current sales
over time report.
- The Analytics View service will provide report data via the authenticated
public API interface.
+ May not need to be coupled to the project, as we may be able to filter the
existing report to distinguish between current sales tracking and landing
page sales.
+ Anything /new/ should be built using the new Analytics View service.

View file

@ -0,0 +1,53 @@
:PROPERTIES:
:ID: 9b3ed74d-41d4-4784-89e3-6a9183903b9e
:END:
#+title: Stripe Payments Service
* Overview
The Stripe Payments service mediates purchases made by buyers from AWeber
customers through the [[id:7d940785-68b9-4da7-bad1-4771d496168c][Stripe payment platform]] and delivers their goods via list
subscription.
* How It Works
** Ordering
The order endpoint prepares a [[https://stripe.com/docs/api/payment_intents][Payment Intent]] with Stripe that the buyer will pay
directly using Stripe's public API. The payment intent captures the amount to be
paid, any fees that AWeber will collect, and account and list metadata used to
fulfill the order.
*** Email validation
Emails are checked against the [[https://gitlab.aweber.io/CP/Services/validation][CP Email Validation Service]] to ensure they can be
added as subscribers successfully. The following steps are performed by the service:
1. Validates that the format of the submitted email address matches AWeber's
internal email formatting rules.
2. Applies address normalization to remove any ISP specific markup.
3. Extracts the domain-part from the normalized address and validates that there
are MX records for the domain-part via DNS lookup.
4. Checks if the both the submitted and normalized version of the email address
would be blocked by the blocklist.
Should the validation service fail unexpectedly or be unavailable at the time an
order is placed, a warning will be logged and only the email format check will
be performed.
*** CAPTCHA
**** Domain validation
***** Allowed domains
The following root domains provided by AWeber for [[https://confluence.aweber.io/pages/viewpage.action?pageId=118885193][Web Content (a.k.a Landing
Pages)]] always pass CAPTCHA domain validation:
- =aweberpages.com=
- =aweb.page=
***** Custom domains
All other domains are checked against the [[https://confluence.aweber.io/display/AR/Custom+Domain+Service][Custom Domain Service]] to ensure they
are owned and managed by the AWeber account from which the purchase is being
made.
*** Fees
Fees are collected for each sale as a percentage of the sale value configured in
the [[https://confluence.aweber.io/display/CT/Service+Limits+API][Service Limits API]] for the AWeber account, rounded to the nearest cent with
a minimum fee of $0.01.
** Fulfillment
*** Adding the subscriber
Subscribers are added to the list configured for the payment if they are not
already subscribed. If the subscriber is currently on the list in an unconfirmed
state, they will be marked as subscribed. Any tags configured for the sale will
be added to the subscriber.
*** Purchase Tracking
A [[https://confluence.aweber.io/display/AR/Pageview][Pageview Event]] is emitted with details of the order so that the purchase is
tracked in the subscriber's activity.

View file

@ -0,0 +1,448 @@
:PROPERTIES:
:ID: a9835afc-e0be-4436-8274-c3898fdf119c
:END:
#+title: Recurring and split Stripe payments
#+options: prop:t
To be implemented in the [[id:9b3ed74d-41d4-4784-89e3-6a9183903b9e][Stripe Payments Service]].
Recurring and split payments are implementable in Stripe as [[https://stripe.com/docs/billing/subscriptions/overview#subscription-lifecycle][subscriptions]]. The
key difference is that split payments have a limited number of payment
iterations (e.g. 5 monthly installments) whereas recurring payments are ongoing.
* Stripe objects
** Products
Represents something being sold.
** Prices
Amount and frequency to be charged for a product. Many prices may be available
for a single product.
** Customers
Represents a buyer (subscriber?). Needs to be configured with a payment method.
** Subscriptions
Represents a [[*Products][Product]] being offered to a [[* Customers][Customer]] at a [[* Prices][Price]].
** Invoices
Generated at billing times.
** Payment Intents
Attempts to pay an [[* Invoices][Invoice]].
* Easy Commerce Flow
** Setup
[[*Products][Products]] and [[* Prices][Prices]] should be set up in the customer's stripe account and
referenced in their landing page.
#+begin_src plantuml :file "ecommerce-products.svg"
actor "AWeber Customer" as customer
participant "Landing Page Editor" as lp
participant "Stripe Payments (Authenticated)" as sp
participant "Stripe" as stripe
== Load product information ==
customer -> lp : Edit landing page
lp -> sp : GET /stripe-authenticated/products
sp -> stripe : Get products with prices
sp -> lp : Return list of products with pricing
== Save product information ==
customer -> lp : Save landing page
alt create a new product & price
lp -> sp : POST /stripe-authenticated/products
else update an existing product & price
lp -> sp : PATCH /stripe-authenticated/products/{UUID}
end
sp -> stripe : Store product and price
sp -> lp : 200 Return product and price IDs
#+end_src
#+RESULTS:
[[file:ecommerce-products.svg]]
** Purchase
#+begin_src plantuml :file "ecommerce-subscribing.svg"
actor "Buyer" as buyer
participant "Landing Page" as lp
participant "Stripe Payments (Unauthenticated)" as sp
box "Internal"
participant "Core API" as capi
end box
participant "Stripe" as stripe
buyer -> lp : Place order
lp -> sp : POST /stripe/subscription with product and price IDs
sp -> stripe : Create customer
sp -> stripe : Create subscription
sp -> stripe : Create initial invoice
sp -> lp : Return payment intent client secret
lp -> stripe : Confirm payment
stripe -> lp : Return payment intent
lp -> sp : POST /stripe/fulfillment
sp -> stripe : Fetch payment intent and check status
sp -> capi : Add subscriber with tags
sp -> lp : 200 OK
#+end_src
#+RESULTS:
[[file:ecommerce-subscribing.svg]]
*** Subscribing
Unlike ordering, which creates a single payment intent, we'll instead need to
take the payment information provided by the buyer and create a [[* Customers][Customer]] object
in Stripe, as well as a [[* Subscriptions][Subscription]] to a [[*Products][Product]] at a specific [[* Prices][Price]].
*** Fulfillment
After a successful payment, the [[* Subscriptions][Subscription]] is marked as =active=, and the
buyer should be subscribed to start receiving their content.
** Automatic payments
#+begin_src plantuml :file "ecommerce-payment-webhooks.svg"
participant "Stripe Payments (Unauthenticated)" as sp
box "Internal"
participant "RabbitMQ" as amqp
end box
participant "Stripe" as stripe
stripe -> sp : POST /stripe/webhooks (payment_intent.succeeded)
sp -> amqp : Sales tracking event (pageview.v4)
sp -> amqp : Payment succeeded event (stripe_payment_succeeded.v1)
sp -> stripe : 200 OK
#+end_src
#+RESULTS:
[[file:ecommerce-payment-webhooks.svg]]
** Payment failure / Cancellation
#+begin_src plantuml :file "ecommerce-cancellation-webhooks.svg"
participant "Stripe Payments (Unauthenticated)" as sp
box "Internal"
participant "Core API" as capi
end box
participant "Stripe" as stripe
stripe -> sp : POST /stripe/webhooks (customer.subscription.updated)
sp -> stripe : Fetch product metadata
sp -> capi : Remove tags from subscriber or unsubscribe
sp -> stripe : 200 OK
#+end_src
#+RESULTS:
[[file:ecommerce-cancellation-webhooks.svg]]
* Questions
- Should stripe-payments or the integrations service handle price and product management?
+ stripe-payments would need authenticated endpoints exposed
* Project :taskjuggler_project:
:PROPERTIES:
:COLUMNS: %50ITEM(Task) %Effort %allocate %task_id %blocker
:start: 2021-02-22
:END:
#+begin: columnview
| Task | Effort | allocate | task_id | blocker |
|-------------------------------------------------------------------+--------+-------------------+-------------------------+-------------------------------------------------------------------------------|
| Project | | | | |
| Tasks | | | | |
| Backend: Product Management | | | | |
| Define API for managing products and prices | 1d | backend | api | |
| Endpoint: Create products and prices | 3d | backend | create | api |
| Endpoint: Update products and prices | 5d | backend | update | api |
| Endpoint: Fetch products and prices | 2d | backend | fetch | api |
| Endpoint: Update /order | 2d | backend | order_both | api |
| Migrate products from existing landing pages into Stripe on save | | frontend | migrate | order_both |
| Backfill products from existing landing pages into Stripe | 10d | backend, frontend | backfill | order_both |
| Backend: Subscriptions | | | | |
| Endpoint: Update /order to create customers for product purchases | 10d | backend | order_products | milestone_management |
| Endpoint: Add subscription support to /order | 10d | backend | order_subscriptions | milestone_management |
| Endpoint: Update /fulfill & /webhooks (iteration 1) | 3d | backend | webhooks_1 | milestone_management |
| Endpoint: Update /webhooks to process subscriptions (iteration 2) | 8d | backend | webhooks_2 | webhooks_1, order_subscriptions |
| Database: Add database for event tracking | 3d | backend | database | |
| Endpoint: Add event tracking to /webhooks | 5d | backend | tracking | database, webhooks_2 |
| Backend: Resiliency | | | | |
| Poller: Create poller to check for unprocessed events | 10d | backend | poller | milestone_subscriptions |
| Backend: Cleanup | | | | |
| Backfill products from existing landing pages into Stripe | 10d | backend, frontend | backfill2 | milestone_subscriptions |
| Endpoint: Update /order remove /fulfill | 1d | backend | cleanup | backfill2 |
| Frontend | | | | |
| Frontend: Plugin | 2d | frontend | plugin | |
| Frontend: Templates | 1d | frontend | templates | plugin |
| Frontend: Builder (Product Management) | 12d | frontend | builder_products | plugin, templates |
| Frontend: Builder (Recurring Payments) | 17d | frontend | builder_recurring | milestone_management |
| Milestones | | | | |
| Milestone 1: Product Management | 5d | release | milestone_management | api, create, update, fetch, order_both, migrate, backfill, builder_products |
| Milestone 2: Support Subscriptions | 5d | release | milestone_subscriptions | order_products, webhooks_1, webhooks_2, database, tracking, builder_recurring |
| Milestone 3: Add Resiliency | | | milestone_resiliency | poller |
| Milestone 4: Deprecate Product Name/Price | | | milestone_cleanup | milestone_subscriptions, cleanup |
#+end:
#+begin_export html
<iframe src="reports/recurring-and-split-stripe-payments/Plan.html" width="100%" height="1000"></iframe>
#+end_export
** Tasks
*** Backend: Product Management
**** DONE Define API for managing products and prices
:PROPERTIES:
:EFFORT: 1d
:ALLOCATE: backend
:task_id: api
:END:
Probably makes sense to treat price + recurrence as product attributes, e.g.
- name :: String
- Price :: Object
+ amount :: Integer
+ currency :: Enum["usd"]
+ recurrence :: Optional[Object]
- interval :: Enum["weekly", "monthly", "yearly"]
- times :: Union["unlimited", Integer]
**** DONE Endpoint: Create products and prices
:PROPERTIES:
:EFFORT: 3d
:ALLOCATE: backend
:blocker: api
:TASK_ID: create
:END:
- Define metadata schema for purchase and cancellation actions (add tags, unsubscribe)
**** DONE Endpoint: Update products and prices
:PROPERTIES:
:EFFORT: 5d
:ALLOCATE: backend
:blocker: api
:TASK_ID: update
:END:
- Add new price object if the price or recurrence has changed, leaving the old one
**** DONE Endpoint: Fetch products and prices
:PROPERTIES:
:EFFORT: 2d
:ALLOCATE: backend
:blocker: api
:TASK_ID: fetch
:END:
**** DONE Endpoint: Update /order
:PROPERTIES:
:EFFORT: 2d
:ALLOCATE: backend
:blocker: api
:task_id: order_both
:END:
**** DONE Migrate products from existing landing pages into Stripe on save
:PROPERTIES:
:ALLOCATE: frontend
:blocker: order_both
:TASK_ID: migrate
:END:
**** CANCELLED Backfill products from existing landing pages into Stripe
:PROPERTIES:
:EFFORT: 10d
:ALLOCATE: backend, frontend
:blocker: order_both
:task_id: backfill
:END:
**** DONE Separate Products and Prices
*** Backend: Subscriptions
**** TODO Endpoint: Create /purchase to create customers for product purchases
:PROPERTIES:
:ALLOCATE: backend
:blocker: milestone_management
:start: 2021-04-22
:Effort: 12d
:TASK_ID: order_products
:END:
If the product being ordered is specified by id:
- Create the customer w/ the provided payment method
- Create an invoice
- Pay the invoice
- Document the /order endpoint as deprecated
- Create acceptance tests
**** TODO Endpoint: Update /fulfill and /webhooks (iteration 1)
:PROPERTIES:
:Effort: 5d
:ALLOCATE: backend
:BLOCKER: milestone_management
:task_id: webhooks_1
:start: 2021-04-22
:END:
- move add subscriber and sales tracking event emission to the webhook endpoint
- no-op /fulfill
- Document /fulfill endpoint as deprecated
- Send =AWeber-Options: update-unconfirmed= header
- Update acceptance tests to account for out-of-band fulfillment
**** TODO Endpoint: Add subscription support to /purchase
:PROPERTIES:
:ALLOCATE: backend
:blocker: milestone_management
:start: 2021-04-22
:Effort: 12d
:TASK_ID: order_subscriptions
:END:
If the product being ordered has a recurring price type:
- Create the customer w/ the provided payment method
- Create a subscription
- Create the initial invoice
- Pay the initial invoice
- Add acceptance tests for subscription purchases
**** TODO Endpoint: Update /webhooks to process subscriptions (iteration 2)
:PROPERTIES:
:Effort: 10d
:ALLOCATE: backend
:blocker: webhooks_1, order_subscriptions
:TASK_ID: webhooks_2
:END:
- Add logic to process a subscription
- Add the events corresponding to cancellation of a subscription
+ Process the remove tag/unsubscribe actions that are saved as metadata on the
subscription
+ Ensure that unconfirmed subscribers can be unsubscribed
- Need a way to differentiate fulfillment of a payment intent for a subscription vs a single payment
+ Pull the invoice for the payment intent with the expanded subscription details if they exist
**** TODO Database: Add database for event tracking
:PROPERTIES:
:Effort: 3d
:ALLOCATE: backend
:BLOCKER: milestone_management
:start: 2021-04-22
:task_id: database
:END:
- new postgres users ( one for the poller, one for the stripe-payments service )
- Needs the following information in an events table:
+ timestamp from the event
+ timestamp of action completion
+ webhook type
+ webhook id
+ stripe account id
+ aweber account id
+ and whether it was successfully processed (process state)
- dynamodb does not fit the use case because we need to retrieve all events in a time frame, and filter by account. We are also constantly deleting events.
+ we also will have two services updating the database and need transaction safety
- Scope assumes handing off the schema design to the DBA team
**** TODO Endpoint: Add event tracking to /webhooks
:PROPERTIES:
:Effort: 7d
:ALLOCATE: backend
:BLOCKER: database, webhooks_2
:TASK_ID: tracking
:END:
- Mark processed events in the database
+ Do we need transactions? Assume yes.
- Handling duplicate and out-of-order event cases
+ Fulfill only once (adding subscriber + tags)
+ Unsubscribe only once (removing subscriber / tags)
+ Don't fulfill if unsubscribed
*** Backend: Resiliency
We will need a job to periodically poll Stripe for unhandled webhooks for
processing.
- This job will need to share a data store with the stripe payments service to
track which webhooks have been processed.
+ Is this necessary, or does stripe expose the processed state of the
webhooks? Webhook attempts and responses are logged in the console.
- The job will need to maintain a lock to prevent concurrent runs.
- How will unhandled webhooks get processed?
+ Send them to the stripe payments service endpoint?
- Would have to store status or update it in Stripe.
+ Have Stripe send them?
- Is this possible?
**** TODO Poller: Create poller to check for unprocessed events
:PROPERTIES:
:EFFORT: 10d
:ALLOCATE: backend
:BLOCKER: milestone_subscriptions
:TASK_ID: poller
:END:
*** Backend: Cleanup
**** TODO Backfill products from existing landing pages into Stripe
:PROPERTIES:
:EFFORT: 10d
:ALLOCATE: backend, frontend
:blocker: milestone_subscriptions
:task_id: backfill2
:END:
**** TODO Endpoint: Remove /order and /fulfill
:PROPERTIES:
:EFFORT: 1d
:ALLOCATE: backend
:blocker: backfill2
:TASK_ID: cleanup
:END:
*** Frontend
Builder originally estimated at 25d combined.
**** TODO Frontend: Plugin
:PROPERTIES:
:Effort: 2d
:ALLOCATE: frontend
:TASK_ID: plugin
:BLOCKER:
:END:
**** TODO Frontend: Templates
:PROPERTIES:
:Effort: 1d
:ALLOCATE: frontend
:TASK_ID: templates
:BLOCKER: plugin
:END:
**** TODO Frontend: Builder (Product Management)
:PROPERTIES:
:EFFORT: 12d
:ALLOCATE: frontend
:TASK_ID: builder_products
:BLOCKER: plugin, templates
:END:
**** TODO Frontend: Builder (Recurring Payments)
:PROPERTIES:
:EFFORT: 28d
:ALLOCATE: frontend
:TASK_ID: builder_recurring
:BLOCKER: milestone_management
:start: 2021-04-22
:END:
** Milestones
*** Milestone 1: Product Management
:PROPERTIES:
:BLOCKER: api, create, update, fetch, order_both, migrate, backfill, builder_products
:TASK_ID: milestone_management
:Effort: 5d
:ALLOCATE: release
:END:
*** Milestone 2: Support Subscriptions
:PROPERTIES:
:BLOCKER: order_products, webhooks_1, webhooks_2, database, tracking, builder_recurring
:TASK_ID: milestone_subscriptions
:Effort: 10d
:ALLOCATE: release
:END:
*** Milestone 3: Add Resiliency
:PROPERTIES:
:BLOCKER: poller
:TASK_ID: milestone_resiliency
:END:
*** Milestone 4: Deprecate Product Name/Price
:PROPERTIES:
:BLOCKER: milestone_subscriptions, cleanup
:TASK_ID: milestone_cleanup
:END:
* Resources :taskjuggler_resource:
*** Backend
:PROPERTIES:
:resource_id: backend
:efficiency: 1.3
:END:
*** Frontend
:PROPERTIES:
:resource_id: frontend
:efficiency: 0.8
:END:
*** Release Validation
:PROPERTIES:
:resource_id: release
:efficiency: 1.0
:END:
* Variables :noexport:
Local Variables:
org-taskjuggler-reports-directory: "reports/recurring-and-split-stripe-payments"
End:

View file

@ -0,0 +1,38 @@
:PROPERTIES:
:ID: 33e47957-b3d0-41c9-8977-7243b42a76dd
:END:
#+title: Control Panel HTTP Requests
#+PROPERTY: header-args :exports both :eval no-export
#+PROPERTY: header-args:http :cookie .cookies :cookie-jar .cookies
* Cookies
| Name | Description |
|-------------+-------------|
| AUTORESPSID | Session ID |
Cookies for requests in this document are stored in cookie file by curl in
=~/.cookies= (https://curl.se/docs/http-cookies.html).
* AJAX Requests
Control Panel controller actions that expect to be called as AJAX endpoints
expect the =X-Requested-With= header to be present and set to =XMLHttpRequest=.
* Logging In
** Fetching a CSRF Token
#+name: login-csrf
#+begin_src http :pretty
GET localhost:8080/users/pub/csrf
X-Requested-With:XMLHttpRequest
#+end_src
#+RESULTS: login-csrf
: 63116e764c5d31cdd3e4f230ee3740527f6eb1c76aea1cb04e30da5d68e24d78
** Sending credentials
#+begin_src http :pretty :var csrf=login-csrf
POST localhost:8080/users/account/loginAjax
X-Requested-With: XMLHttpRequest
username=lookatme@example.com&password=testing&_csrf=${csrf}
#+end_src
#+RESULTS:
: {"submitStatus":{"code":200,"message":"\/users\/","category":"status_success"},"validationErrors":[]}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,383 @@
:PROPERTIES:
:ID: 6af95849-8f78-4697-ab48-3712ff2f5ee1
:END:
#+title: Stripe payments tracking database
#+OPTIONS: ^:nil
Database for tracking payments and subscriptions managed by the [[id:9b3ed74d-41d4-4784-89e3-6a9183903b9e][Stripe Payments Service]].
* State Tracking
** Legacy purchase
- Client initiates purchase
- Backend coordinates with Stripe and returns a payment intent
+ Track new purchase and related incomplete payment
- Client completes purchase with Stripe
- Stripe notifies backend of payment intent status (update purchase)
+ Track event
+ Track payment as completed
+ Fulfill purchase
+ Track purchase as fulfilled
#+begin_src plantuml :file stripe-legacy-purchase-tracking.svg
participant Stripe
actor Client
participant "stripe-payments" as Backend
database Tracking
Client -> Backend: Initiate purchase
Backend -> Tracking : <font color="blue">Store incomplete purchase</font>
Backend -> Stripe : Create payment intent
Backend -> Tracking : <font color="blue">Store incomplete payment</font>
Backend -> Client : Return payment intent
Client -> Stripe : Complete purchase
...
alt Success
Stripe ---> Backend : payment_intent.succeeded
Backend -> Tracking : <font color="green">Store event</font>
alt Event not previously handled
Backend -> Tracking : <font color="blue">Mark payment as succeeded</font>
Backend -> Backend : Fulfill purchase
Backend -> Tracking : <font color="blue">Mark purchase as fulfilled</font>
end
else Failure
Stripe --> Backend : payment_intent.payment_failed
Backend -> Tracking : <font color="green">Store event</font>
end
#+end_src
#+RESULTS:
[[file:stripe-legacy-purchase-tracking.svg]]
#+caption: Legacy purchase flow with events
#+RESULTS:
** Product purchase
- Client prepares payment method with Stripe
- Client initiates purchase
- Backend coordinates with Stripe to complete the purchase
+ Attaches the payment method to the customer
+ For single products
- Creates an invoice item
- Creates an invoice
- Pays the invoice
+ For subscriptions
- Creates the subscription
#+begin_src plantuml :file stripe-purchase-tracking.svg
participant Stripe
actor Client
participant "stripe-payments" as Backend
database Tracking
Client -> Stripe : Create payment method
Client -> Backend: Initiate purchase
Backend -> Tracking : <font color="blue">Store incomplete purchase</font>
alt Non-recurring
Backend -> Stripe : Create invoice item
Backend -> Stripe : Create invoice
Backend -> Stripe : Pay invoice
Backend -> Tracking : <font color="blue">Store completed payment</font>
else Recurring
Backend -> Stripe : Create subscription
Backend -> Tracking : <font color="blue">Store active subscription</font>
end
...
Stripe --> Backend : Subscription activated
...
alt
Stripe --> Backend : customer.subscription.updated (no longer active)
else
Stripe --> Backend : subscription_schedule.canceled
end
Backend -> Tracking : <font color="green">Store event</font>
Backend -> Backend : Trigger unsubscribe actions
Backend -> Tracking : <font color="blue">Mark subscription as terminated</font>
#+end_src
#+caption: Product purchase flow
#+RESULTS:
[[file:stripe-purchase-tracking.svg]]
#+begin_src plantuml :file stripe-purchase-tracking-payment-events.svg
participant Stripe
actor Client
participant "stripe-payments" as Backend
database Tracking
== Payment succeeded ==
Stripe --> Backend : payment_intent.succeeded
Backend -> Tracking : <font color="green">Store event</font>
alt Event not previously handled
Backend -> Tracking : <font color="blue">Mark payment as succeeded</font>
Backend -> Stripe : Look up invoice
note over Backend
Recurring if an invoice exists
and has an associated subscription
end note
alt Non-recurring
Backend -> Backend : Fulfill purchase
Backend -> Tracking : <font color="blue">Mark purchase as fulfilled</font>
else Recurring
end
end
== Payment failed ==
Stripe --> Backend : payment_intent.payment_failed
Backend -> Tracking : <font color="green">Store event</font>
#+end_src
#+caption: Payment events
#+RESULTS:
[[file:stripe-purchase-tracking-payment-events.svg]]
#+begin_src plantuml :file stripe-purchase-subscription-events.svg
participant Stripe
actor Client
participant "stripe-payments" as Backend
database Tracking
== Subscription activated ==
alt
Stripe --> Backend : customer.subscription.created (active)
else
Stripe --> Backend : customer.subscription.updated (active)
end
Backend -> Tracking : <font color="green">Store event</font>
alt New subscription
Backend -> Tracking : <font color="blue">Mark subscription as active</font>
Backend -> Backend : Fulfill purchase
Backend -> Tracking : <font color="blue">Mark purchase as fulfilled</font>
else Subscription already processed
note over Backend
One of the following is true:
<font color="green">- Already received an activation event for this subscription</font>
<font color="green">- Already receieved a termination event for this subscription</font>
<font color="blue">- Subscription already marked as active or terminated</font>
<font color="blue">- Purchase already marked as fulfilled</font>
end note
end
== Subscription terminated ==
alt
Stripe --> Backend : customer.subscription.updated (no longer active)
else
Stripe --> Backend : subscription_schedule.canceled
end
Backend -> Tracking : <font color="green">Store event</font>
alt Active subscription
Backend -> Backend : Trigger unsubscribe actions
Backend -> Tracking : <font color="blue">Mark subscription as terminated</font>
else Subscription not yet processed
note over Backend
One of the following is true:
<font color="green">- Did not receive an activation event for this subscription</font>
<font color="blue">- Subscription is not tracked</font>
end note
else Subscription already terminated
note over Backend
One of the following is true:
<font color="green">- Already received a termination event for this subscription</font>
<font color="blue">- Subscription already marked as terminated</font>
end note
end
#+end_src
#+caption: Subscription events
#+RESULTS:
[[file:stripe-purchase-subscription-events.svg]]
* Tables
** Purchases
Purchases made via the [[id:9b3ed74d-41d4-4784-89e3-6a9183903b9e][Stripe Payments Service]].
#+begin_src plantuml :file stripe-purchase-states.svg
[*] -> New
New -> Fulfilled
#+end_src
#+caption: Purchase state diagram
#+RESULTS:
[[file:stripe-purchase-states.svg]]
#+caption: Purchases
| Field | Type | Nullable | Description |
|----------------+-----------+----------+--------------------------------------------|
| id | UUID | N | Auto-generated purchase ID |
| created | timestamp | N | Time the purchase was initiated |
| last_updated | timestamp | N | Time the purchase was last updated |
| account | UUID | N | The AWeber customer account purchased from |
| stripe_account | text | N | The Stripe account of the AWeber customer |
| fulfilled | boolean | N | Was this purchase fulfilled |
- Store which automations were applied & when
** Subscriptions and Split Payments
Purchases made with recurring payments, managed using a Stripe Subscription.
#+begin_src plantuml :file stripe-subscription-states.svg
[*] -> New
New -> Active
Active --> Terminated : Payment Failed
Active --> Terminated : Unsubscribed
New --> Terminated : Payment Failed
#+end_src
#+caption: Subscription state diagram
#+RESULTS:
[[file:stripe-subscription-states.svg]]
#+caption: Subscriptions
| Field | Type | Nullable | Description |
|----------------+---------------------+----------+---------------------------------------------------------|
| id | text | N | The Stripe subscription ID |
| created | timestamp | N | Time the purchase was initiated |
| last_updated | timestamp | N | Time the purchase was last updated |
| account | UUID | N | The AWeber customer account the subscription belongs to |
| stripe_account | text | N | The Stripe account of the AWeber customer |
| status | [[subscription_status][subscription_status]] | N | Status of the subscription |
<<subscription_status>>
#+caption: ENUM: Subscription Status
| new |
| active |
| terminated |
** Payments
Payments collected from buyers.
#+begin_src plantuml :file stripe-payment-states.svg
[*] -> New
New --> Paid : Payment succeeded
New --> Failed : Payment failed
#+end_src
#+caption: Payment state diagram
#+RESULTS:
[[file:stripe-payment-states.svg]]
#+caption: Payments
| Field | Type | Nullable | Description |
|----------------+----------------+----------+-------------------------------------------|
| id | text | N | The Stripe payment intent ID |
| created | timestamp | N | Time the purchase was initiated |
| last_updated | timestamp | N | Time the purchase was last updated |
| account | UUID | N | The AWeber customer account that was paid |
| stripe_account | text | N | The Stripe account of the AWeber customer |
| purchase | UUID | N | |
| status | [[payment_status][payment_status]] | N | Status of the payment intent |
<<payment_status>>
#+caption: ENUM: Payment Status
| new |
| paid |
| failed |
** Events
#+caption: Events
| Field | Type | Nullable | Description |
|----------------+-----------+----------+---------------------------------------------------------|
| id | text | N | Stripe event id |
| type | text | N | Stripe event type |
| account | UUID | N | The AWeber customer account the subscription belongs to |
| stripe_account | text | N | The Stripe account of the AWeber customer |
| timestamp | timestamp | N | Time event was published |
| subscription | text | Y | Related Stripe subscription ID |
Events should expire out of the database over time. Stripe maintains events in
its database for up to 30 days.
*** Modeling in DynamoDB
**** Table
- Partition Key :: =id=
The table index provides for rapid lookup of individual events.
**** Attributes
| Field | Type | Required | Description |
|----------------+-------------------------+----------+-----------------------------------------------------------|
| id | String | Y | Stripe event id |
| type | String | Y | Stripe event type |
| stripe_account | String | Y | The Stripe account of the AWeber customer |
| timestamp | String (timestamp) | Y | Time event was published (e.g. "2020-01-01 01:02:34.567") |
| date | String (date) | Y | Date portion of timestamp (e.g. "2020-01-01") |
| time | String (time) | Y | Time portion of timestamp (e.g. "01:02:34.567") |
| expiration | Number (Unix timestamp) | Y | TTL expiration time of the event record |
| subscription | String | N | Related Stripe subscription ID |
**** TTL
The events table will automatically expire and remove rows for which
=expiration= has passed. We may set =expiration= to be the event creation time
plus thirty days to match Stripe's own expiration. This will prevent old items
from piling up in the table.
**** Time index (GSI)
- Partition Key :: =date=
- Sort Key :: =time=
- Projection :: Keys Only
The date and time portions of the event timestamp will be separated to allow for
scanning all events within a day, filterable by time. This should support our
polling job, which should run multiple times per day. We'll have to be careful
around date boundaries when looking up events (e.g. by including a query for
events from the previous day during the first run of the current day).
**** Stripe Account index (GSI)
- Partition Key :: =stripe_account=
- Sort Key :: =timestamp=
- Projection :: Include type, subscription
The account index provides for rapid lookup of events for a particular stripe
account, filterable by timestamp.
**** Subscription index (GSI)
- Partition Key :: =subscription=
- Sort Key :: =timestamp=
- Projection :: Include type, stripe_account
The account index provides for rapid lookup of events for a particular
subscription, filterable by timestamp.
**** IAM Policy
Put together by referencing [[id:ab2d34bf-97b1-4e50-8e9a-597d0f8fcf01][DynamoDB IAM Policies]].
#+caption: Stripe Payments DynamoDB IAM Policy
#+begin_src json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "dynamodb:ListTables",
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"dynamodb:BatchGetItem",
"dynamodb:BatchWriteItem",
"dynamodb:ConditionCheckItem",
"dynamodb:PutItem",
"dynamodb:DeleteItem",
"dynamodb:Scan",
"dynamodb:Query",
"dynamodb:UpdateItem",
"dynamodb:DescribeTimeToLive",
"dynamodb:CreateTable",
"dynamodb:DescribeTable",
"dynamodb:GetItem",
"dynamodb:UpdateTable",
"dynamodb:UpdateTimeToLive"
],
"Resource": [
"arn:aws:dynamodb:*:018154689201:table/*-stripe-payments-*/index/*",
"arn:aws:dynamodb:*:018154689201:table/*-stripe-payments-*"
]
},
]
}
#+end_src
* Notes
- How does the [[id:83d61eef-0781-46e0-b959-1a739cff5ea3][poller]] use these stored events?
+ /To identify events retrieved from Stripe which do not need to be replayed/
- How do we replay events?
+ Send it to the webhook endpoint? An internal webhook endpoint that shares
code with the public one?
- /The same webhook endpoint would be preferable, requires signing the
payload/
- Do we expose the processed events as an internal endpoint in the
stripe-payments service, or give the polling app a database connection?
+ /API seems preferable/
Following some discussion, the [[Events][Events]] table should be sufficient for now.

View file

@ -0,0 +1,12 @@
:PROPERTIES:
:ID: fe6374ac-8fa6-4579-bd42-6d4de92de86a
:END:
#+title: Finding number of subscribers with a tag
* Retrieve number of subscribers per tag
- Tagging -> Kubernetes
- Add appdb connection to tagging
- Add endpoint to tagging
- Expose tagging endpoints via authenticated kong
* Hide tags
* Delete tags

View file

@ -0,0 +1,23 @@
:PROPERTIES:
:ID: 8435e743-c092-43e9-bcc6-a8098aa4110c
:END:
#+title: Tagging Roadmap
* Retrieve number of subscribers per tag
** Migrate the tagging service from AWS to Kubernetes
** Expose tagging endpoints via authenticated kong
** Add appdb connection to tagging
** Create an AppDB user for the tagging service
- Full access to the subscriber_tags table
- Read access to the list.subscribers table
** Add endpoint to tagging to fetch subscribers on a tag
e.g.:
#+begin_example
GET tagging.service.production.consul/{tag}/account/{account}/subscribers
#+end_example
** Update the tagging service to update the subscriber_tags table directly.
** Retire the subscriber-tag-sync consumer
* Hide tags

View file

@ -0,0 +1,43 @@
:PROPERTIES:
:ID: 193f7c04-0a03-4870-90c8-2b5e3c4c92ce
:END:
#+title: Moving pages out of Sites
#+filetags: :project:
A [[id:db322997-ff5e-416a-8dc8-f29e6a4928c8][Technical Initiative]] to rewrite pages in the [[id:57ee2f00-9bcd-4e0f-8a77-ae1f2d4cda89][Control Panel]] still built in PHP.
* Pages in Sites
** TODO Dashboard
*** Broadcasts
*** Lists
*** Subscribers
In progress.
** Content Creation
*** TODO Follow-ups
*** TODO Blog Broadcasts
*** TODO Email Template Manager
** Subscribers
*** TODO Manage Subscribers
*** TODO Add Subscribers
*** TODO Import History
** Reports
*** TODO Classic Reports
*** TODO Report API
*** TODO Tracking
** Lists
*** TODO Manage Lists
*** TODO List Settings
*** TODO Custom Fields
*** TODO List Automations
** Accounts
*** TODO My Account
*** Billing
*** Notifications
** Advocates
- Create an affiliate management API
- Create an affiliate frontend application
- Embed the affiliate app in the CP and link them with CP accounts.
** TODO Help
Should this be separated?
** Integrations
😱

View file

@ -0,0 +1,8 @@
:PROPERTIES:
:ID: db322997-ff5e-416a-8dc8-f29e6a4928c8
:END:
#+title: Technical Initiative
* Active
- [[id:193f7c04-0a03-4870-90c8-2b5e3c4c92ce][Moving pages out of Sites]]
- [[id:b4f579f7-f848-4a7b-b7bc-f34fec36346a][Cleaning up public endpoints in proxy services]]

View file

@ -0,0 +1,29 @@
:PROPERTIES:
:ID: b4f579f7-f848-4a7b-b7bc-f34fec36346a
:END:
#+title: Cleaning up public endpoints in proxy services
A [[id:db322997-ff5e-416a-8dc8-f29e6a4928c8][Technical Initiative]] to move endpoints exposed in "proxy" services into more
relevant services and instead expose them via Kong.
* Proxy Services
** subscriber-proxy
*** TODO =/<subscriber_id>=
*** TODO =/batch_delete=
*** TODO =/batch_tag=
*** TODO =/batch_unsubscribe=
*** TODO =/bulk-tagging/jobs=
*** TODO =/bulk-tagging/jobs/<id>=
** search-proxy
** tagging-proxy
** search-recipients

View file

@ -0,0 +1,4 @@
:PROPERTIES:
:ID: 76933c22-fe7c-43e9-9ec9-62564377dd85
:END:
#+title: Imbi

View file

@ -0,0 +1,13 @@
:PROPERTIES:
:ID: 9332ed8f-b669-4d3f-a25d-da751a8c2da1
:END:
#+title: Troubleshooting an unresolvable kubernetes service hostname
The service's selector wasn't matching any pods, therefore the service had no IP
to respond with. This can be troubleshooted by inspecting the endpoints for the
service. Due to the mismatch, none were available.
The ClusterIP service did not itself have an IP assigned even after fixing the
mismatch. This appears to be an optimization in that Kubernetes won't bother
assigning an IP to the service if the service has only a single endpoint, as it
is more expedient to return the sole endpoint's IP address.

View file

@ -0,0 +1,15 @@
:PROPERTIES:
:ID: e2dab290-3e1b-4d5a-8628-61c2cb4896dc
:END:
#+title: Purchase tracking
* Related Confluence documents
- [[https://confluence.aweber.io/pages/viewpage.action?spaceKey=PD&title=2021-03-16+Record+purchase+details+for+AWeber+integrations][2021-03-16 Record purchase details for AWeber integrations]]
- [[https://confluence.aweber.io/pages/viewpage.action?spaceKey=API&title=Storing+PayPal+purchases+as+analytics+events][Storing PayPal purchases as analytics events]]
* Record purchases
- Track a separate event type for sales tracking.
- Changes to sales tracking URLs can break tracking currently.
- Separate URL profiles for Stripe and Paypal? (Stripe is currently using
ECommerce), and add an ECommerce event type for them.
* Segmenting on purchases

View file

@ -0,0 +1,65 @@
:PROPERTIES:
:ID: ab2d34bf-97b1-4e50-8e9a-597d0f8fcf01
:END:
#+title: DynamoDB IAM Policies
#+caption: DynamoDB access for the k8s-labs-application role
#+begin_src json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "dynamodb:ListTables",
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"dynamodb:BatchGetItem",
"dynamodb:BatchWriteItem",
"dynamodb:ConditionCheckItem",
"dynamodb:PutItem",
"dynamodb:DeleteItem",
"dynamodb:Scan",
"dynamodb:Query",
"dynamodb:UpdateItem",
"dynamodb:DescribeTimeToLive",
"dynamodb:CreateTable",
"dynamodb:DescribeTable",
"dynamodb:GetItem",
"dynamodb:UpdateTable"
],
"Resource": [
"arn:aws:dynamodb:*:018154689201:table/*-webhook-callbacks/index/*",
"arn:aws:dynamodb:*:018154689201:table/*-webhook-callbacks"
]
},
{
"Effect": "Allow",
"Action": [
"dynamodb:BatchGetItem",
"dynamodb:BatchWriteItem",
"dynamodb:ConditionCheckItem",
"dynamodb:PutItem",
"dynamodb:DeleteItem",
"dynamodb:Scan",
"dynamodb:Query",
"dynamodb:UpdateItem",
"dynamodb:DescribeTimeToLive",
"dynamodb:CreateTable",
"dynamodb:DescribeTable",
"dynamodb:GetItem",
"dynamodb:UpdateTable"
],
"Resource": [
"arn:aws:dynamodb:*:018154689201:table/*-webhooks",
"arn:aws:dynamodb:*:018154689201:table/*-webhooks/index/*"
]
}
]
}
#+end_src
- [[https://docs.amazonaws.cn/en_us/amazondynamodb/latest/developerguide/access-control-overview.html][Overview of Managing Access Permissions to Your Amazon DynamoDB Resources]]
- [[https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/api-permissions-reference.html][DynamoDB API Permissions: Actions, Resources, and Conditions Reference]]

View file

@ -0,0 +1,41 @@
:PROPERTIES:
:ID: bdc526d1-4f57-4210-93f2-12bb30d33ed9
:END:
#+title: Rebuild Unsubscribe Page as a React Application
#+filetags: :project:
* Flows
As described in [[https://confluence.aweber.io/display/~scottm/Unsubscribe+Flows][Unsubscribe Flows]]:
- View subscriptions
- Edit subscriptions
- Edit subscriber
+ Name
+ Email
+ Custom fields, if present
** Questions
- What do we do when the backend (database) is unavailable?
+ The backend could do some sort of queuing if the database is unavailable
(similar to the disk-persisted queue currently in use).
* Frontend
A new React application to support subscriber subscription management actions.
** Questions
- What will host the frontend application?
+ Deploy the application to S3 and have F5 route to it.
* Backend
A new python service to handle subscriber subscription management actions.
** Endpoints
*** Configuration
Returns account information and branding needed to display the page.
*** Get subscriptions
*** Edit subscriptions
*** Edit subscriber
Email verification needs to be processed if the subscriber updates their email
address in order for that change to take effect (addresses [[https://jira.aweber.io/browse/CCPANEL-11269][CCPANEL-11269]]).
Changes to other fields should be applied immediately.
**** Questions
- Do custom field changes need verification to change?
+ Probably not.
- Should we display a pending state until verification is complete?
+ We do not plan to store a pending state.

View file

@ -0,0 +1,13 @@
:PROPERTIES:
:ID: d0a802dd-3258-4a86-b53f-287f7f6df6e6
:END:
#+title: Cobrowse.io
#+filetags: :project:
- Allows CS team to view a session of one of our customers.
- This will initially be available to all customers as a link on the help page.
- User-initiated, share the code with the AW agent.
- We will be using their cloud solution.
- Look into an API for adding admin notes that the session took place
- Look into information to be redacted in session recordings
+ Information displayed will have to be tagged with a CSS class to be identified

View file

@ -0,0 +1,12 @@
:PROPERTIES:
:ID: af4ae6ee-5201-49ee-aa01-6cf6a0801908
:END:
#+title: Migrating AWS services
#+filetags: :project:
A [[id:db322997-ff5e-416a-8dc8-f29e6a4928c8][Technical Initiative]] to migrate services from ECS deployments to Kubernetes
deployments. The purpose is to homogenize our deployments to use Kubernetes,
which could theoretically be deployed to our local Kubernetes cluster or to EKS.
We currently maintain two separate sets of clusters in AWS. Services should be
migrated to the new cluster, or to kubernetes, ideally.

View file

@ -0,0 +1,977 @@
:PROPERTIES:
:ID: 022406d2-0480-4470-90d0-9533f6b9fa32
:END:
#+title: Supporting multiple currencies in Stripe