How to approach Redux + Microservices intercommunication - redux

We are implementing an auction cloud service, that will receive orders, from an external API service on demand. Each received order is a 1:1 to an auction.
We can have more than 2000 orders (auctions) per day.
we have decided to use Microservices + Redux to separate concerns between orders and auctions.
Bellow are the explanation of each service.
External API
Enternal API is just a website that pushes orders to our Order Service and receive updates from our Order service we have no control over it.
Order service
Orders has a bunch of information (properties) that the client (mobile app) use to get information to decide on joining an auction. for example, this is how an order can look like:
{
id: 123,
description: 'Some description',
salePrice: 0,
minPrice: 1000,
openPrice: 500,
status: 'active',
address: 'Some address',
file: '.../some-file.pdf',
client: 'Joe Doe',
notes: 'Some notes',
createdAt: '12345678',
pending: false,
postpone: false,
...moreproperties
}
In the order service the orders can be updated (address, name, openPrice, minPrice, status, etc) by the server at any time before the auction starts via the actions bellow.
{ type: LOAD_ORDERS, orders }
{ type: PEND_ORDER, id }
{ type: POSTPONE_ORDER, id }
{ type: SET_ORDER_AUCTION, id, auction, salePrice }
{ type: UPDATE_ORDER, id, properties }
Auction Service
An auction object in this service can look like this:
{
id: 'abcd',
orderId: 123456,
increment: 1,
outBid: { agentId: 'b1', price: 545 },
bestBid:{agentId: 'b2', price: 550 },
openPrice: 500,
currentPrice: 550,
status: 'started'
startedByAgent: 'a1'
}
Auctions can be updated by these actions:
{ type: JOIN_AUCTION, id, agentId, type }
{ type: START_AUCTION, id, agentId }
{ type: PLACE_BID, id, agentId, price }
{ type: END_AUCTION, id, agentId }
API Service
It works just as gateway between front end app and the microservices. Receive and dispatch requests from clients (mobiles) to Order Service or Auction Service in form of actions.
Workflow:
1 - External API push orders of the day to Order Service via LOAD_ORDERS also a CREATE_AUCTIONS action is dispatched to the Action Service to create an auction for each order.
2 - User open mobile app and get the list of orders of the day with details including open prices from Order Service.
3 - User join an specific order
- API Service creates a bidder agent that will place bids.
- API Service send a join action via JOIN_AUCTION to join an auction on the Auction Service
4 - An auctioneer agent starts the auction and bidding starts.
5 - Joined bidder agents starts to place bids via PLACE_BID action on Auction Service.
6 - When auction is over the auctioneer agent ends the auction by dispatching END_AUCTION.
7 - When auction ends sale price and auction details (via object) are send to the Order Service via the SET_ORDER_AUCTION.
8 - The Order Service handle the SET_ORDER_AUCTION and update the order state with the final salePrice and the auction object and then wait for payment.
9 - Once payment info is received from the client then it is submitted to the External Service by Order Service
My questions are:
Is the workflow above a reasonable approach for using Microservices + Redux and updating each service state?
It is ok to dispatch actions from a microservice to another when using redux microservices? My question is because when using microservices + event sourcing + CQRS, services intercommunication are not recommended but instead using a Sagas that work as intermediate service that convert events to commands.
My other question is where to put business logic (validation), for example a bidder cannot send bid if auction is not started or its already ended, a bidder cannot send a bid if he/she has not joined the auction yet. Were to put this logic? in action, middleware or reducers? and how to handle errors back to clients?
In general what are some best practices when it comes to Microservices + Redux?
What are pros and cons of using Microservices + Redux vs Microservices + Event sourcing + CQRS?
Sorry for the long post, I just need some orientation here because I cannot find any documentation about this topic and I am not sure if I am approaching this right.
Any advice would be appreciated!!!

I think this questions is way off the guidelines.
Nevertheless, I'd say write your own redux middleware to integrate your microservices; the API is very simple:
store => next => action => {
let result = next(action)
return result
}
In the most outer function (store =>) you can add microservice listerner and dispatch actions via the store reference.
In the innermost function (action =>) you can react to actions in the redux app by sending requests to your microservices.

Related

NextJs Stripe Subscription with subscription end date

I have generated some products in stripe configured as recurring payment and i can subscribe to them without problem.
I would like to know if it is possible from the same call that is made to generate the subscription it is possible to indicate the end date of this subscription.
Researching the documentation I see that they talk about "cancel_at" but I can't find where to indicate this parameter in the call. I haven't been able to locate any examples either.
const session = await stripe.checkout.sessions.create({
payment_method_types: ["card"],
line_items: [
{
price: "price_1LaMHQH4AS5eXWlNhznAR7JA",
quantity: 1
},
],
mode: 'subscription',
success_url:`${req.headers.origin}/public/payment?success=true`,
cancel_url: `${req.headers.origin}/public/payment?cancelled=true`
});
You cannot provide an end date for a Subscription in a Checkout Session.
What you can do instead is to listen for the checkout.session.completed webhook event. This event will contain the subscription id and upon receipt of the event, you can make a request to update that Subscription's cancel_at.

AWS 10DLC cross account

I have a lambda function, SNS topics/subscriptions in Account A, I have a 10DLC campaign/number in Account B
How do I have the lambda function be able to pass a origination id so I can use the 10DLC number? right now I am using SDK v2 and using publish method. I know I will also need to assume a role - would I be right then to assume since I am assuming a pinpoint send role from Account B - my lambda would not be able to use the SNS topic/subscription from account A?
I see these prop here: AWS.MM.SMS.OriginationNumber from https://docs.aws.amazon.com/sns/latest/dg/sms_publish-to-phone.html#sms_publish_sdk
But cannot find where to use it on the SDK v3 documentation page
I can't speak to the role part of your question, but as far as using the Origination Number, I can give an example because I just set this up yesterday.
Here's a Node example for specifying an origin number when sending sms:
const AWS = require('aws-sdk')
AWS.config.update({ region: 'us-east-1' })
const SNS = new AWS.SNS({ apiVersion: '2010-03-31' })
const params = {
Message: "This is the message you are sending",
PhoneNumber: "+13334445555", // This is the receiving number
MessageAttributes: {
'AWS.SNS.SMS.SMSType': {
DataType: 'String',
StringValue: 'Transactional' // Can also be set as Promotional
},
'AWS.MM.SMS.OriginationNumber': {
DataType: 'String',
StringValue: "+15556667777" // This is the send number
}
}
}
return SNS.publish(params).promise()

Stripe API: How do you find the exchange rate and amount in foreign currency of a cross border transfer to a Connect Account?

I'm using Stripe to process payments. I have a platform where international Connect accounts can sell items to others through my platform.
My platform is USD. In this specific example, the Connect account is in CAD (Canada Dollar). When someone purchases an item from this CAD Connect Account, Stripe puts the money in my platform account, leaves my app fee there, and then moves the correct amount into the CAD Connect account. It converts this amount to CAD. In the stripe GUI I can find the exchange rate and amount transferred in CAD, as circled in the screenshot below. But I can't find these attributes in the API.
The only object I've seen with an exchange_rate attribute is the balance transaction. However, when I get the balance transaction for the transaction in the screenshot, I get this response object:
Request: https://api.stripe.com/v1/balance_transactions/txn_1IBNiNLLBOhef2QNqIeaNg9o
Response:
{
"id": "txn_1IBNiNLLBOhef2QNqIeaNg9o",
"object": "balance_transaction",
"amount": -7777,
"available_on": 1611619200,
"created": 1611076199,
"currency": "usd",
"description": null,
"exchange_rate": null,
"fee": 0,
"fee_details": [],
"net": -7777,
"reporting_category": "transfer",
"source": "tr_1IBNiNLLBOhef2QNcNqv3IlS",
"status": "pending",
"type": "transfer" }
The problem here is that the balance transaction object above only shows this transaction in USD: $77.77 USD came out of my platform account.
But it doesn't show the conversion rate or the amount in CAD. When this $77.00 went into the CAD Connect account, as we can see in the GUI screenshot, that $77.77 was converted to $98.02 CAD and the exchange rate was 1.26039 (USD->CAD).
How can I find this CAD amount and exchange rate through the API?
In case you need to know the currency rate before the actual transaction happens they provide the currency rates here
HOWEVER, it's only a frontend page and it requires authorization. On top of that, it's updated once every 12 hours. There is no official API that returns currency rates.
There is a small SaaS product that provides stripe currency rates as a REST API.
https://striperates.com/
The Transfer made to the connected account in this screenshot is in USD. The conversion happens after the Transfer itself. The funds are sent in USD and then in the connected account they get converted to that account's default Currency.
You want to look at the Transfer's destination_payment property which has the py_123 id for the Charge on the connected account. That Charge has a BalanceTransaction object (similar to the Transfer one you shared). That BalanceTransaction would reflect the conversion from USD to CAD and surface the exchange rate used in the exchange_rate property in that case.
In Java you can do
Stripe.apiKey = "sk_live_...";
Transfer transfer = Transfer.retrieve("tr_...");
RequestOptions requestOptions = RequestOptions.builder().setStripeAccount("acct_...").build();
Charge charge = Charge.retrieve(transfer.getDestinationPayment(), requestOptions);
BalanceTransaction balanceTransaction = BalanceTransaction.retrieve(charge.getBalanceTransaction(), requestOptions);
balanceTransaction.getExchangeRate();
balanceTransaction.getNet();
balanceTransaction.getCurrency();
Here is the way I did : I simulated a payment in a foreign currency to a US based account (so that it is presented in US$), then I retrieved the balance transaction associated, and the exchange_rate argument
async function exchangeFeesToUS(currency) {
const paymentIntent = await stripeTestUS.paymentIntents.create({
amount: 10000,
currency: currency,
payment_method_types: ['card'],
customer: "ANY_CUSTOMER_WITH_A_PAYMENT_METHOD",
payment_method: "ANY_CARD_ASSOCIATED_TO_THIS_CUSTOMER",
confirm: true
});
return await balanceTransaction(paymentIntent).then(balance => {
return balance.exchange_rate
})
}
async function balanceTransaction(paymentIntent) {
const chargeId = paymentIntent.charges.data[0].id;
const charge = await stripeTestUS.charges.retrieve(
chargeId
);
return await stripeTestUS.balanceTransactions.retrieve(
charge.balance_transaction
);
}
Now, if you want to convert to another currency than the US$, just call the aforementioned function twice :
async function exchangeFeesToCUSTOM_CURRENCY(currency) {
const rate = await exchangeFeesToUS(currency)
const rateCUSTOM_CURRENCY = await exchangeFeesToUS('your_custom_currency')
return rate / rateCUSTOM_CURRENCY
}

How to properly configure Amplify Analytics?

I need some help understanding how to configure AWS Pinpoint analytics in Amplify. I'm currently using Amplify for Auth and have it configured like this in my index.js file:
export const configureAmplify = () => {
window.LOG_LEVEL="DEBUG"
Hub.listen('auth', data => handleAmplifyHubEvent(data))
Hub.listen('analytics', data => handleAmplifyHubEvent(data))
Amplify.configure({
Auth: {
identityPoolId: "redacted",
region: "us-west-2",
userPoolId: "redacted",
userPoolWebClientId: "redacted",
mandatorySignIn: false,
cookieStorage: {
domain: process.env.NODE_ENV === 'development' ? "localhost" : "redacted",
path: '/',
expires: 1,
secure: false
}
}
})
}
To add Analytics, I started by adding this to my configureAmplify() function:
Analytics: {
disabled: false,
autoSessionRecord: true,
AWSPinpoint: {
appId: 'redacted',
region: 'us-east-1',
endpointId: `wgc-default`,
bufferSize: 1000,
flushInterval: 5000, // 5s
flushSize: 100,
resendLimit: 5
}
}
Upon user sign-in or refresh from cookie storage I called
Analytics.updateEndpoint({
address: user.attributes.email, // The unique identifier for the recipient. For example, an address could be a device token, email address, or mobile phone number.
attributes: {
},
channelType: 'EMAIL', // The channel type. Valid values: APNS, GCM
optOut: 'ALL',
userId: user.attributes.sub,
userAttributes: {
}
})
After doing this, it seems to me that the data in the Pinpoint console is not accurate. For example, there are currently 44 sessions displayed when no endpoint filter is applied. If I add an endpoint filter by userAttributes: userId then no matter which ID I select, it shows all 44 sessions associated with that user. I suspect that is because the EndpointID is established at startup, and is not changed by the updateEndpoint call.
I have also tried omitting the Analytics key when I initially configure Amplify, and then calling Analytics.configure() after the user is signed in. With this approach, I can construct a user-specific endpointId. However, I think that doing it this way will mean that I don't capture any of the Authentication events (sign-in, sign-up, auth failure), because Analytics is not configured until after they occur.
So my question is what is the proper timing for configuring Amplify Analytics? How can I accurately capture session, auth and custom events, AND uniquely identify them by user id?
It's not necessary to assign a custom endpoint id, amplify will handle it automatically and all events will be tracked per device. Instead, if you really need it, update the endpoint with the userId after sign-in.
The advantage of adding the userId is that all the endpointIds of a user are automatically associated to that userId, thus when you update a user's attribute, it will be synchronized across the endpoints.
As you are using Cognito, Amazon Cognito can add user IDs and attributes to your endpoints automatically. For the endpoint user ID value, Amazon Cognito assigns the sub value that's assigned to the user in the user pool. To learn about adding users with Amazon Cognito, see Using Amazon Pinpoint Analytics with Amazon Cognito User Pools in the Amazon Cognito Developer Guide.

How do I delete user analytics data from Firebase using userDeletionRequests:upsert?

Problem Description
My Android app collects data via Google Analytics for Firebase. For privacy reasons, users must be able to wipe their data off the Firebase servers, should they choose to do so.
The app requests a deletion by forwarding its Firebase APP_INSTANCE_ID to my own server. This server has been prepared in advance with credentials, from my personal Google account (via oauth2), for managing the Firebase project. The server authenticates with www.googleapis.com, and, using the supplied APP_INSTANCE_ID, invokes the upsert.
As noted by the documentation, the generic Google Analytics API is appropriate for this task.
After some initial trouble (b/c I didn't have the correct auth scope, and the Analytics API wasn't properly enabled), googleapis.com now returns HTTP 200 for each upsert request. (As an aside, even if you supply a bogus APP_INSTANCE_ID, it returns 200.)
Here is a sample response from the upsert, which shows nothing amiss:
{ kind: 'analytics#userDeletionRequest',
id:
{ type: 'APP_INSTANCE_ID',
userId: (REDACTED 32-char hexidecimal string) },
firebaseProjectId: (REDACTED),
deletionRequestTime: '2018-08-28T12:46:30.874Z' }
I know the firebaseProjectId is correct, because if I alter it, I get an error. I have verified that the APP_INSTANCE_ID is correct, and stable up until the moment it is reset with resetAnalyticsData().
Test Procedure
To test the deletions, I populated Firebase with several custom events, using the procedure below (Nexus 5X emulator, no Google Play, no Google accounts configured, but that shouldn't make any difference):
Install the app
Fire off some custom events (FirebaseAnalytics.logEvent)
Observe those events appear on the Firebase console
(About a minute later:) Make the upsert call, observe HTTP 200, and note the "deletionRequestTime"
Immediately call FirebaseAnalytics.resetAnalyticsData (to clear any event data cached on the device)
Uninstall the app
Rinse & repeat 7 or 8 times
However, even 24 hours later, 100% of the Firebase events are still present in the events table. No discernable state change has taken place on the Firebase server as a result of the upserts.
Question
So, what am I doing wrong? how do I successfully delete user data from Google Analytics for Firebase?
EDIT
Here's the code I'm using to make a request (from node.js):
const request = require( 'request' );
...
_deletePersonalData( data )
{
return new Promise( (resolve, reject) => {
request.post({
url: 'https://www.googleapis.com/analytics/v3/userDeletion/userDeletionRequests:upsert',
body: {
kind: 'analytics#userDeletionRequest',
id: {
type: 'APP_INSTANCE_ID',
userId: data.firebaseAppInstanceId
},
firebaseProjectId: (REDACTED)
},
headers: {
Authorization: 'Bearer ' + iap.getCurAccessToken()
},
json: true
}, (err, res, body) => {
console.log( 'user-deletion POST complete' );
console.log( 'Error ' + err );
console.log( 'Body ', body );
if( err )
{
reject( err );
return;
}
if( body.error )
{
reject( new Error( 'The Google service returned an error: ' + body.error.message + ' (' + body.error.code + ')' ) );
return;
}
resolve({ deletionRequestTime: body.deletionRequestTime });
});
});
}
Here's a sample request body:
{
kind: 'analytics#userDeletionRequest',
id: {
type: 'APP_INSTANCE_ID',
userId: (REDACTED 32-char hexidecimal string)
},
firebaseProjectId: (REDACTED)
}
And here's the console output for that same request (same userId and everything):
user-deletion POST complete
Error: null
Body: { kind: 'analytics#userDeletionRequest',
id:
{ type: 'APP_INSTANCE_ID',
userId: (REDACTED 32-char hexidecimal string) },
firebaseProjectId: (REDACTED),
deletionRequestTime: '2018-08-29T17:32:06.949Z' }
Firebase support just got back to me, and I quote:
Upsert method deletes any individual user data we have logged, but aggregate metrics are not recomputed. This means that you might not see any changes in the events tab in your Analytics console.
So, basically my mistake was expecting the events to disappear from the console.
This, of course, raises the question of how one determines that the API is actually working... but maybe the HTTP 200 is enough.

Resources