Braintree PAyment create customer and save payment method - braintree

I am trying to save the card information and create customer at same time. The client app creates a nonce and sends it to my server (nodejs) and I call:
gateway.customer.create({paymentMethodNonce: request.params.nonce})
The customer gets created and I get a customer ID, I save that in the db. But calling
gateway.customer.find(customer_id):`
returns:
Customer {
id: '697983269',
merchantId: 'yzkvrqy9qsctn69d',
firstName: null,
lastName: null,
company: null,
email: null,
phone: null,
fax: null,
website: null,
createdAt: '2017-09-25T00:37:29Z',
updatedAt: '2017-09-25T00:37:29Z',
customFields: '',
creditCards: [],
addresses: [],
paymentMethods: [] }
Which has empty payment method array. I am using the drop in UI that only asks for card number and exp date. This is also a sandbox account.

Full disclosure: I work at Braintree. If you have any further questions, feel free to contact
support.
Hi Mehdi. I took a closer look at your Braintree Sandbox, and while you are in fact creating customer records in your Vault, it appears that there is no nonce value being passed into the customer creation API call, resulting in a customer record containing only a customer ID.
Would you mind logging, and relaying here, the value you get on your server for your request.params.nonce and ensure it contains a valid nonce string from your client?
Don't hesitate to reach out to support if you need further clarity.

Related

Stripe Subscription:Customer Creation

I am working on stripe subscription integration in my Spring Boot application. I am able to successfully redirect user to the checkout page and process the payment. I am working on the no-code application and the business model of my app is to charge the customer for each project created. Each time the user process the payment I am saving the customer-id and subscription-id in the database of the project but in order to subscribe to the same customer for the next project I have to create a new customer in the Stripe account and then the same flow continues. So, is it possible to subscribe the same customer for a new project without creating the customer in the stripe account?
I don't see why not. You just need to pass the existing customer Id when creating a checkout session.
That's what I'm doing:
If a user doesn't have a stripe customer created, I create one and store the customer id against my internal user id (I also send the user id in the customer metadata to stripe, so it's easy to correlate them, as they may use different email addresses for payment).
If a customer already exists for the current user, I just use that one.
I'm using typescript, the concept is the same:
// uid is my internal user id
const customerId = getOrCreateCustomer(uid);
const newSession: StripeType.Checkout.SessionCreateParams = {
customer: customerId,
mode: 'subscription',
payment_method_types: ['card'],
customer_update: {
address: 'auto',
name: 'auto',
shipping: 'auto',
},
subscription_data: {
metadata: { uid }, // store uid in subscription metadata as well
},
line_items: [
{
price: priceId,
quantity: 1,
}
],
success_url: successUrl,
cancel_url: cancelUrl,
};

Way to limit number of records a user can create in Amplify GraphQL API

I have an app where Auth is implemented using Cognito User Pools and API is a GraphQL API implemented using Amplify. In the Schema definitions, is there an easy way to limit the number of records a user can create. For example in the following schema...
type Product #model #auth(rules: [{ allow: owner }]) {
id: ID!
name: String!
description: String
}
I would like to limit the users to a maximum of 100 Products.
One way is via my front-end. When I detect that a user has reached 100 limit, I can just make the UI stop giving them the ability to add more. But if someone were to bypass the UI, they could create more than 100. Hence, I prefer to enforce this limit in the backend.
Is there a way to do this in the Schema definition, or elsewhere in AWS / DynamoDB ?
Thanks!
There isn't a straightforward way to do this that I'm aware of.
Below is how I would solve this.
Create a #key on Product on the owner property, so that you can query by owner.
Overwrite the CreateProduct mutation. In your custom resolver, before creating a new Product, query the Product table byOwner, using the owner id passed in, to count how many already exist.
Here is the documentation: https://docs.amplify.aws/cli/graphql-transformer/resolvers#add-a-custom-geolocation-search-resolver-that-targets-an-elasticsearch-domain-created-by-searchable
I think the easiest solution would be processing the API request in a lambda function that validates the request (product count < 100) before having the script write to the DB. Then you can null out the built-in mutations for the model to prevent unintended access.
Example Schema:
type Mutation {
addProduct(input: ProductAddInput): ProductAddOutput #function(name: "productLambda-${env}")
}
type Product
#model(queries: null, mutations: null, subscriptions: null) /* update these to what you need */
#auth(rules: [{ allow: owner }]) {
id: ID!
name: String!
description: String
}
In Lambda you can pull the username from the event.identity property and that should correlate to the owner field in the db. Since the AWS package is automatically loaded you should be looking at very fast script execution as long as your db indexes are set properly.
For the user product count, I see a couple of options:
A secondary index set up on the owner field so you don't do a ton of
scans
If you have a user table, you could add a field that counts
the products for each user and just update that table any time you
update the product table.

Nested GraphQL mutations with AWS Amplify/AppSync

I've reached out on the AWS forums but am hoping to get some attention here with a broader audience. I'm looking for any guidance on the following question.
I'll post the question below:
Hello, thanks in advance for any help.
I'm new to Amplify/GraphQL and am struggling to get mutations working. Specifically, when I add a connection to a Model, they never appear in the mock api generator. If I write them out, they say "input doesn't exist". I've searched around and people seem to say "Create the sub item before the main item and then update the main item" but I don't want that. I have a large form that has several many-to-many relationships and they all need to be valid before I can save the main form. I don't see how I can create every sub item and then the main.
However, the items are listed in the available data for the response. In the example below, addresses, shareholders, boardofdirectors are all missing in the input.
None of the fields with '#connection' appear in the create api as inputs. I'll take any help/guidance I can get. I seem to not be understanding something core here.
Here's my Model:
type Company #model(queries: { get: "getEntity", list: "listEntities" }, subscriptions: null) {
id: ID!
name: String!
president: String
vicePresident: String
secretary: String
treasurer: String
shareholders: Shareholder #connection
boardOfDirectors: BoardMember #connection
addresses: [Address]! #connection
...
}
type Address #model{
id: ID!
line1: String!
line2: String
city: String!
postalCode: String!
state: State!
type: AddressType!
}
type BoardMember #model{
id: ID!
firstName: String!
lastName: String!
email: String!
}
type Shareholder #model {
id: ID!
firstName: String!
lastName: String!
numberOfShares: String!
user: User!
}
----A day later----
I have made some progress, but still lacking some understanding of what's going on.
I have updated the schema to be:
type Company #model(queries: { get: "getEntity", list: "listEntities" }, subscriptions: null) {
id: ID!
name: String!
president: String
vicePresident: String
secretary: String
treasurer: String
...
address: Address #connection
...
}
type Address #model{
id: ID!
line1: String!
line2: String
city: String!
postalCode: String!
state: State!
type: AddressType!
}
I removed the many-to-many relationship that I was attempting and now I'm limited to a company only having 1 address. I guess that's a future problem. However, now in the list of inputs a 'CompanyAddressId' is among the list of inputs. This would indicate that it expects me to save the address before the company. Address is just 1 part of the company and I don't want to save addresses if they aren't valid and some other part of the form fails and the user quits.
I don't get why I can't write out all the fields at once? Going along with the schema above, I'll also have shareholders, boardmembers, etc. So I have to create the list of boardmembers and shareholders before I can create the company? This seems backwards.
Again, any attempt to help me figure out what I'm missing would be appreciated.
Thanks
--Edit--
What I'm seeing in explorer
-- Edit 2--
Here is the newly generated operations based off your example. You'll see that Company takes an address Id now -- which we discussed prior. But it doesn't take anything about the shareholder. In order to write out a shareholder I have to use 'createShareholder' which needs a company Id, but the company hasn't been created yet. Thoroughly confused.
#engam I'm hoping you can help out the new questions. Thank you very much!
Here are some concepts that you can try out:
For the #model directive, try it out without renaming the queries. AWS Amplify gives great names for the automatically generated queries. For example to get a company it will be getCompany and for list it will be listCompanys. If you still want to give it new names, you may change this later.
For the #connection directive:
The #connection needs to be set on both tables of the connection. Also if you want many-to-many connections you need to add a third table that handles the connections. It is also usefull to give the connection a name, when you have many connections in your schema.
Only Scalar types that you have created in the schema, standard schalars like String, Int, Float and Boolean, and AWS specific schalars (like AWSDateTime) can be used as schalars in the schema. Check out this link:
https://docs.aws.amazon.com/appsync/latest/devguide/scalars.html
Here is an example for some of what I think you want to achieve:
type Company #model {
id: ID!
name: String
president: String
vicePresident: String
secretary: String
treasurer: String
shareholders: [Shareholder] #connection(name: "CompanySharholderConnection")
address: Address #connection(name: "CompanyAdressConnection") #one to many example
# you may add more connections/attributes ...
}
# table handling many-to-many connections between users and companies, called Shareholder.
type Shareholder #model {
id: ID!
company: Company #connection(name: "CompanySharholderConnection")
user: User #connection(name: "UserShareholderConnection")
numberOfShares: Int #or String
}
type User #model {
id: ID!
firstname: String
lastname: String
company: [Shareholder] #connection(name: "UserShareholderConnection")
#... add more attributes / connections here
}
# address table, one address may have many companies
type Address #model {
id: ID!
street: String
city: String
code: String
country: String
companies: [Company] #connection(name: "CompanyAdressConnection") #many-to-one connection
}
Each of this type...#model generates a new dynamoDB table. This example will make it possible for u to create multiple companies and multiple users. To add users as shareholders to a company, you only need to create a new item in the Shareholder table, by creating a new item with the ID in of the user from the User table and the ID of the company in the Company table + adding how many shares.
Edit
Be aware that when you generate a connection between two tables, the amplify cli (which uses cloudformation to do backend changes), will generate a new global index to one or more of the dynamodb tables, so that appsync can efficient give you data.
Limitations in dynamodb, makes it only possible to generate one index (#connection) at a time, when you edit a table. I think you can do more at a time when you create a new table (#model). So when you edit one or more of your tables, only remove or add one connection at a time, between each amplify push / amplify publish. Or else cloudformation will fail when you push the changes. And that can be a mess to clean up. I have had to, multiple times, delete a whole environment because of this, luckily not in a production environment.
Update
(I also updated the Address table in the schema with som values);
To connect a new address when you are creating a new company, you will first have to create a new address item in the Address table in dynamoDb.
The mutation for this generated from appsync is probably named createAddress() and takes in a createAddressInput.
After you create the address you will recieve back the whole newly createdItem, including the automatically created ID (if you did not add one yourself).
Now you may save the new company that you are creating. One of the attributes the createCompany mutation takes is the id of the address that you created, probably named as companyAddressId. Store the address Id here. When you then retrieves your company with either getCompany or listCompanys you will get the address of your company.
Javascript example:
const createCompany = async (address, company) => {
// api is name of the service with the mutations and queries
try {
const newaddress = await this.api.createAddress({street: address.street, city: address.city, country: address.country});
const newcompany = await this.api.createCompany({
name: company.name,
president: company.president,
...
companyAddressId: newaddress.id
})
} catch(error) {
throw error
}
}
// and to retrieve the company including the address, you have to update your graphql statement for your query:
const statement = `query ListCompanys($filter: ModelPartiFilterInput, $limit: Int, $nextToken: String) {
listCompanys(filter: $filter, limit: $limit, nextToken: $nextToken) {
__typename
id
name
president
...
address {
__typename
id
street
city
code
country
}
}
}
`
AppSync will now retrive all your company (dependent on your filter and limit) and the addresses of those companies you have connected an address to.
Edit 2
Each type with #model is a referance to a dynamoDb table in aws. So when you are creating a one-to-many relationship between two tables, when both items are new you first have to create the the 'many' in the one-to-many realationships. In the dynamoDb Company tables when an address can have many companies, and one company only can have one address, you have to store the id (dynamoDB primary key) for the address on the company. You could of course generate the address id in frontend, and using that for the id of the address and the same for the addressCompanyId in for the company and use await Promise.all([createAddress(...),createCompany(...)) but then if one fails the other one will be created (but generally appsync api's are very stable, so if the data you send is correct it won't fail).
Another solution, if you generally don't wont to have to create/update multiple items in multiple tables, you could store the address directly in the company item.
type Company #model {
name: String
...
address: Address # or [Address] if you want more than one Address on the company
}
type Address {
street: String
postcode: String
city: string
}
Then the Address type will be part of the same item in the same table in dynamoDb. But you will loose the ability to do queries on addresses (or shareholders) to look up a address and see which companies are located there (or simulary look up a person and see which companies that person has a share in). Generally i don't like this method because it locks your application to one specific thing and it's harder to create new features later on.
As far as I'm aware of, it is not possible to create multiple items in multiple dynamoDb tables in one graphql (Amplify/AppSync) mutation. So async await with Promise.all() and you manually generate the id attributes frontendside before creating the items might be your best option.

Unable to get gender from Google People API

I've got a problem obtaining gender information from People API.
I'm making a request to https://people.googleapis.com/v1/people/account_id which is not returning gender field. If I add genders to personFields it is giving me Requested entity was not found error.
It looks like obtaining this information is forbidden. Is there any chance to get this field?
There are two ways to use the Google People api.
The first assumes that you have used Oauth2 to authenticate your user.
GET https://people.googleapis.com/v1/people/me
returns the info about the current authenticated user.
The second is a public call to the api. you can use an API key or Oauth2.
GET https://people.googleapis.com/v1/people/117200475532672775346
This will return the info about a specific user {117200475532672775346} but it will depend upon what that user has set to public. The above number is my personal g+ account, the following is the gender response.
"genders": [
{
"metadata": {
"primary": true,
"source": {
"type": "PROFILE",
"id": "117200475532672775346"
}
},
"value": "female",
"formattedValue": "Female"
}
I have no idea where you are getting your account id this is a users google id. The information must be filled out on Google plus i suggest you check the users google+ account to see what they have set to public https://plus.google.com/u/0/117200475532672775346. Note: It doesn't matter if this is the current authenticated user if they dont have the info set public you cant see it in your application.
Tip: assuming you only want to see genders you can use the fields parameter to request just that
?fields=genders

Is Braintree_Customer::create() returns credit card object on success

Is Braintree_Customer::create() returns credit card object in braintree on success?
It returns on failure in verification object.
I want to know the best practice to access credit card object if it's present in response in case of success and failure of Braintree_Customer::create().
Seems like you can access the newly created customer's payment method within the successful result object (Python);
My customer create call:
result = braintree.Customer.create({
'first_name': 'John',
'last_name': 'Smith',
'company': 'Internet',
'email': 'john#example.com',
'payment_method_nonce':'fake-valid-nonce'
})
so
result.customer.payment_methods
will return an array containing the newly created payment method at index 0, which is essentially Braintree's Credit Card Result object, which contains all of the appropriate attributes for that credit card object.

Resources