Stripe React Native Subscriptions - ios

I'm stuck on this issue:
When I try to submit this subscription, I got an error of "subscription_payment_intent_requires_action", should I handle it on client-side or there is another way to do it?
For information: I already created setupIntent (verify 3D Secure) and return payment_method
Steps:
1 - create setupIntent
2 - generate payment_method ( verify 3D Secure)
3 - create Customer and attach payment_method to it
4 - create Subscription (on back_end) => error: "subscription_payment_intent_requires_action" shows up!
Thank you all!
// Create Subscription With Payment Method + Customer ID
router.post('/createSubscription', function (req, res){
const {payment_method, customerId} = req.body;
var stripe = require("stripe")(stripe_sk);
try{
stripe.subscriptions
.create({
customer: customerId,
items: [{
plan: 'plan_HTGCI8ljPYFTHQ'
}],
default_payment_method: payment_method,
expand: ["latest_invoice.payment_intent"],
// enable_incomplete_payments: true
}).then(subscription => {
res.send({
subscription : subscription
})
}).catch(err => {
res.send({
err
})
})
} catch (error) {
res.send("Error : ", error);
}
});```

Related

Not able to add friendly name to participants when creating participant with conversation

I am using twilio for creating an chat API using conversion API (Twilio), I was able to create the converstaion and add new participant to the converstion, But when I try to add friendly name to participant, Its not adding.
client.conversations.conversations(conversionsSID)
.participants
.create({
identity: identity,
FriendlyName: name,
attributes: JSON.stringify({
profileImage: profileImage
})
}).then((participant) => {
resolve({ participant: participant, error: null })
}).catch((error) => {
reject({ participant: null, error: error });
});
I have tried with FriendlyName and friendly_name, both of that does't work.
The participant resource does not have a FriendlyName property. You can see the available properties that you can use in the documentation for creating a conversation participant.
You are already using the attributes property to store a profile image, so you could use this to store your friendly name too. So, you could change your code to:
client.conversations.conversations(conversionsSID)
.participants
.create({
identity: identity,
attributes: JSON.stringify({
name: name,
profileImage: profileImage
})
}).then((participant) => {
resolve({ participant: participant, error: null })
}).catch((error) => {
reject({ participant: null, error: error });
});
You can do like this:
At first CreateConversation after that call update to the user
Client.conversations.users("US1f1ff79756794a28b55fbb1c8ac2b150").fetch().then((user) => {
user.update({ friendlyName: "r2d2" })
.then()
.catch(error => {
console.log(error);
});
});
cheers!

Expo, React Native, Stripe: Setting up future payment methods not working

I’m in desperate need for help.
So I have a side project for an iOS app using Expo / React Native. And I'm having issues with setting up future payment methods using Stripe & Expo’s stripe library.
Our back-ender set up a graphql back-end, and provides me with all the variables I need. I’m trying to set up future payments to charge clients later, but I’m having trouble having with the paymentIntentSheet not showing up after creating an intent and fetching the clientSecret, ephemeralKey and customerId from our back-end. Now i don’t know where the issue is.. Is it because of me using the wrong versions? Maybe incorrect installation? Are the variables I’m using right..?
I used the following documentation page(s) as a guide:
https://stripe.com/docs/payments/save-and-reuse?platform=react-native
https://github.com/stripe/stripe-react-native#expo
These are the version numbers of the libraries I’m using, relevant to this topic/issue:
"expo": "~41.0.1",
"react": "16.13.1",
"react-dom": "16.13.1",
"react-native": "https://github.com/expo/react-native/archive/sdk-41.0.0.tar.gz",
"#stripe/stripe-react-native": "0.1.1"
These are the steps I took:
Install stripe-react-native, and add it to my app.json as a plugin:
"plugins": [
[
"#stripe/stripe-react-native",
{
"merchantIdentifier": "",
"enableGooglePay": false
}
]
],
On global level, I import the StripeProvider component and pass down the given publishable key:
pk_live_51[.....]
On global level it’ll look like this:
<StripeProvider
publishableKey="pk_live_51[...]"
>
<AuthProvider>
<ApolloProvider client={client}>
<InnerApp />
</ApolloProvider>
</AuthProvider>
</StripeProvider>
Then according to the stripe docs, at the component where I'll be setting up future payments, I am supposed to fetch the setupIntent, ephemeralKey, and the customer from the back-end. In this case, it's done in the useEffect of my component. I was provided with a graphql mutation to obtain these values:
mutation (
$createUserPaymentMethodSetupIntentInput: CreateUserPaymentMethodSetupIntentInput!
) {
createUserPaymentMethodSetupIntent(
input: $createUserPaymentMethodSetupIntentInput
) {
setupIntentId
clientSecret
customerId
ephemeralKeySecret
}
}
I then call the function that will eventually provide me with all the necessary variables:
createIntent({
variables: {
createUserPaymentMethodSetupIntentInput: {
userUid: userUid,
},
},
})
.then((res) => {
const clientSecret =
res.data.createUserPaymentMethodSetupIntent.clientSecret
const setupIntentId =
res.data.createUserPaymentMethodSetupIntent.setupIntentId
const ephemeralKeySecret =
res.data.createUserPaymentMethodSetupIntent.ephemeralKeySecret
const customerId =
res.data.createUserPaymentMethodSetupIntent.customerId
// IGNORE THIS FOR NOW
initializePaymentSheet(
clientSecret,
setupIntentId,
ephemeralKeySecret,
customerId
)
})
.catch((err) => console.log({ graphqlError: err }))
The function gives me the following response:
Object {
"data": Object {
"createUserPaymentMethodSetupIntent": Object {
"__typename": "CreatedUserPaymentMethodSetupIntent",
"clientSecret": "seti_1K[....]",
"customerId": "cus_[...]",
"ephemeralKeySecret": "ek_live_[...]",
"setupIntentId": "seti_[...]",
},
},
According to the docs, I should use the setupIntent, ephemeralKey, and customer values as variables in one of their given functions/hooks called “initPaymentSheet” which should initialize the paymentsheet on their end.
These functions are imported like this:
const { initPaymentSheet, presentPaymentSheet } = useStripe();
In step 3, you see that I call a function that then calls the initPaymentSheet after successfully fetching the values from the server.
initializePaymentSheet(
clientSecret,
setupIntentId,
ephemeralKeySecret,
customerId
)
The initializePaymentSheet function looks like this:
const initializePaymentSheet = (
clientSecret,
setupIntentId,
ephemeralKeySecret,
customerId
) => {
initPaymentSheet({
customerId: customerId,
customerEphemeralKeySecret: ephemeralKeySecret,
setupIntentClientSecret: setupIntentId,
})
.then((res) => {
console.log(res)
setDisabledButton(false)
})
.catch((err) => console.log("error.."))
}
As you can see, I call the initPaymentSheet hook there, exactly like shown on the docs, and pass in the values i received from the back-end. However, after doing this i get the following error in the console:
Object {
"error": Object {
"code": "Failed",
"message": "You must provide the paymentIntentClientSecret",
},
}
This didn’t seem like a huge error, so I went ahead and changed the initPaymentSheet parameters by adding the paymentIntentClientSecret field and passed in the clientSecret value which wasn’t previously used:
initPaymentSheet({
customerId: customerId,
customerEphemeralKeySecret: ephemeralKeySecret,
setupIntentClientSecret: setupIntentId,
paymentIntentClientSecret: clientSecret
})
.then((res) => {
console.log(res)
setDisabledButton(false)
})
.catch((err) => console.log("little error.."))
After calling the function and seeing the error disappear, and the console.log shown above logs the following in the console:
Object {
"paymentOption": null,
}
I didn’t think too much of this, and thought it says null just because I have no previously set paymentOptions. I was just happy there were no more errors.
In the .then chain, you see that i enable a button that basically allows a user to call a function that would present a payment sheet where users can submit their paymentMethod. This button is disabled, because I think you should initialize the paymentSheet first before enabling it?
<WideButton
disabled={disabledButton}
text="Add New Payment Method"
clicked={openPaymentSheet}
/>
Anyways, now that the button is finally enabled, the user can click on it and it'll call the following function:
const openPaymentSheet = async () => {
setDisabledButton(true)
const { error, paymentOption } = await presentPaymentSheet()
if (error) {
console.log(error)
setDisabledButton(false)
Alert.alert(`Error code: ${error.code}`, error.message)
}
if (paymentOption) {
setDisabledButton(false)
Alert.alert(
"Success",
"Your payment method is successfully set up for future payments!"
)
console.log(paymentOption)
}
}
Now to quote the stripe docs:
When your customer taps the Set up button, call presentPaymentSheet() to open the sheet. After the customer completes setting up their payment method for future use, the sheet is dismissed and the promise resolves with an optional StripeError.
So, that's exactly what I did: Call the presentPaymentSheet, but then i get the following error:
Object {
"code": "Failed",
"message": "There was an unexpected error -- try again in a few seconds",
}
Now this is where I’m stuck, because it doesn’t provide me with any more information than given above. I’ve tried looking everywhere, and some resources tell me that I should update my stripe, some say i should add stripe to my plugins in app.json. I’ve done all of that and I can’t still figure it out.
Here is a video showing you the behavior in action:
https://user-images.githubusercontent.com/29804130/146274443-82c581ba-8913-4c87-ad2e-5b8719680fed.mov
Here is the code of the entire component:
// steps
// 1. call graphql query to set up intent, retrieve the clientsecret and setupintentid
// 2. call stripes initPaymentSheet's function and pass in useruid, clientsecret and setupintentid
// 3. when initpaymentsheet is ready, enable button for user to add payment information
// 4. Retrieve the payment information and call the createpaymentmethod mutation
// 5. disable button again, and refresh page
export default function PaymentMethods({ userUid }) {
const { initPaymentSheet, presentPaymentSheet } = useStripe()
const [disabledButton, setDisabledButton] = useState(false)
const [createIntent, { data, loading, error }] = useMutation(
ADD_PAYMENT_METHOD_INTENT
)
useEffect(() => {
createUserPaymentMethodIntent()
}, [])
const createUserPaymentMethodIntent = () => {
setDisabledButton(true)
createIntent({
variables: {
createUserPaymentMethodSetupIntentInput: {
userUid: userUid,
},
},
})
.then((res) => {
console.log(res)
const clientSecret =
res.data.createUserPaymentMethodSetupIntent.clientSecret
const setupIntentId =
res.data.createUserPaymentMethodSetupIntent.setupIntentId
const ephemeralKeySecret =
res.data.createUserPaymentMethodSetupIntent.ephemeralKeySecret
const customerId =
res.data.createUserPaymentMethodSetupIntent.customerId
initializePaymentSheet(
clientSecret,
setupIntentId,
ephemeralKeySecret,
customerId
)
})
.catch((err) => console.log({ graphqlError: err }))
}
const initializePaymentSheet = (
clientSecret,
setupIntentId,
ephemeralKeySecret,
customerId
) => {
initPaymentSheet({
customerId: customerId,
customerEphemeralKeySecret: ephemeralKeySecret,
setupIntentClientSecret: setupIntentId,
paymentIntentClientSecret: clientSecret,
})
.then((res) => {
console.log(res)
setDisabledButton(false)
})
.catch((err) => console.log("little error.."))
}
const openPaymentSheet = async () => {
setDisabledButton(true)
const { error } = await presentPaymentSheet()
if (error) {
Alert.alert(`Error code: ${error.code}`, error.message)
} else {
Alert.alert(
"Success",
"Your payment method is successfully set up for future payments!"
)
}
}
return (
<ScrollView>
<PaymentMethodList userUid={userUid} />
<WideButton
disabled={disabledButton}
text="Add New Payment Method"
clicked={openPaymentSheet}
/>
</ScrollView>
)
}
someone plz help :(
you might want to check the logs in your Stripe Dashboard (Dashboard -> Developers -> Logs). From there you'll be able to see more info about this error,

Ionic 4 - In-app purchase product information can't display before purchase

I have google and follow online tutorials on in-app purchase. I have finished all the setting on Apple's platform and am now testing the in-app purchase function. The function seems working but I am now struggling with 2 issues.
First, I use the store.refresh() and store.get() command to fetch the data from Apple's server. However, I am only able to see the fetched product information after I press the "purchase" button. From the Xcode console, I can see the product information likes pricing, etc. already loaded but just can't display on my view page before I press the purchase button. Did I miss anything?
Second, how to modify my codes if I have more than one product in in-app purchase? I have seen some online resources but seems nothing for ionic and in-app-purchase2 that works.
I have been working on this in-app purchase for 2 weeks especially those time consuming setup in Apple. Any help on the above would be great and highly appreciated!
upgrade.ts file
import { NavController, Platform } from '#ionic/angular';
import { InAppPurchase2, IAPProduct } from '#ionic-native/in-app-purchase-2/ngx';
#Component({
selector: 'app-upgrade',
templateUrl: './upgrade.page.html',
styleUrls: ['./upgrade.page.scss'],
})
export class UpgradePage {
setupReady = 'Not yet';
public prod: any = {};
public product: any = {
name: 'Upgrade to Premium 12 months',
appleProductId: 'upgrade_12months',
googleProductId: 'android.test.purchased'
};
constructor(private store: InAppPurchase2,
public platform: Platform,
) {
this.platform.ready().then(() => {
this.configurePurchasing();
this.setupReady = 'Ready';
});
}
ionViewDidEnter() {
this.prod = this.store.get('upgrade_12months');
}
configurePurchasing() {
if (!this.platform.is('cordova')) { return; }
let productId;
try {
if (this.platform.is('ios')) {
productId = this.product.appleProductId;
} else if (this.platform.is('android')) {
productId = this.product.googleProductId;
}
// Register Product
// Set Debug High
this.store.verbosity = this.store.DEBUG;
// Register the product with the store
this.store.register({
id: 'upgrade_12months',
alias: 'upgrade_12months',
type: this.store.PAID_SUBSCRIPTION
});
this.registerHandlers(productId);
this.store.refresh();
this.prod = this.store.get(productId);
InAppPurchase2.getPlugin().ready().then((status) => {
console.log(JSON.stringify(this.store.get(productId)));
console.log('Store is Ready: ' + JSON.stringify(status));
console.log('Products: ' + JSON.stringify(this.store.products));
});
// Errors On The Specific Product
this.store.when(productId).error( (error) => {
alert('An Error Occured' + JSON.stringify(error));
});
// Refresh Always
console.log('Refresh Store');
this.store.refresh();
} catch (err) {
console.log('Error On Store Issues' + JSON.stringify(err));
}
}
registerHandlers(productId) {
// Handlers
this.store.when(productId).approved( (product: IAPProduct) => {
alert('Approved!');
// Purchase was approved
product.finish();
});
this.store.when(productId).registered( (product: IAPProduct) => {
console.log('Registered: ' + JSON.stringify(product));
console.log(` Registered2 ${product.owned}`);
});
this.store.when(productId).updated( (product: IAPProduct) => {
console.log('Loaded' + JSON.stringify(product));
});
this.store.when(productId).cancelled( (product) => {
alert('Purchase was Cancelled');
});
// Overall Store Error
this.store.error( (err) => {
console.log('Store Error ' + JSON.stringify(err));
});
}
async purchase() {
if (!this.platform.is('cordova')) { return; }
let productId;
if (this.platform.is('ios')) {
productId = this.product.appleProductId;
} else if (this.platform.is('android')) {
productId = this.product.googleProductId;
}
this.registerHandlers(productId);
try {
const product = this.store.get(productId);
console.log('Product Info: ' + JSON.stringify(product));
this.store.order(productId).then((p) => {
alert('Purchase Action Detected');
this.registerHandlers(productId);
}).catch((e: string) => {
alert('Error Ordering From Store' + e);
});
} catch (err) {
console.log('Error Ordering ' + JSON.stringify(err));
}
}
restore() {
this.store.refresh();
}
upgrade.html
<ion-content padding>
<ion-row text-center>
<ion-col>Status: <b>{{ this.setupReady }}</b></ion-col>
</ion-row>
<br>
{{ ' Description: '}} {{ this.prod.description }}
<br>
{{ 'Price:' }} {{ this.prod.price }}
<br>
<div margin-vertical text-center>
{{ this.prod.title }}
<ion-button (click)='purchase()' expand="block">
{{ ' Buy now - ' }}
{{ this.prod.price }}
</ion-button>
</div>
<ion-button full icon-left color="secondary" (click)="restore()">
<ion-icon name="refresh"></ion-icon>Restore Purchases
</ion-button>
</ion-content>
I finally solved this problem as probably none of the online tutorials on in-app purchase has ever mentioned these critical points.
First, you must register the products and handlers as soon as the app starts, i.e. on the startup page, NOT on the page you show in-app purchase products to your customers.
Second, register products only ONCE and never unregister them again. Do NOT attempt to register products again in another page.
These should solve many issues you face for in-app purchase integration. Hope this help many others that were struggling on in-app purchase coding.

Apple Pay - "Payment Not Completed" - Using Stripe

I am using stripes "Payment Request Button" to implement Apple Pay for my website. On the stripe side of things all is well. The token is passed through correcty as I verified within Stripe logs.
https://stripe.com/docs/stripe-js/elements/payment-request-button
However, I get an error message: "Payment Not Completed" from Apple Pay every time I try to complete a test payment.
This has me stuck and I'm not sure how to debug or fix. Any ideas?
I get an undefined token
This is the error:
My set up:
FRONT END:
<script src="https://js.stripe.com/v3/"></script>
<div id="payment-request-button">
<!-- A Stripe Element will be inserted here. -->
</div>
<script>
var stripe = Stripe('pk_test_xxxxx');
var paymentRequest = stripe.paymentRequest({
country: 'US',
currency: 'usd',
total: {
label: 'JobQuiz',
amount: 999,
},
requestPayerName: true,
requestPayerEmail: false,
});
var elements = stripe.elements();
var prButton = elements.create('paymentRequestButton', {
paymentRequest: paymentRequest,
});
// Check the availability of the Payment Request API first.
paymentRequest.canMakePayment().then(function(result) {
if (result) {
prButton.mount('#payment-request-button');
} else {
document.getElementById('payment-request-button').style.display = 'none';
}
});
paymentRequest.on('token', function(ev) {
// Send the token to your server to charge it!
fetch('/apple-pay', {
method: 'POST',
body: JSON.stringify({token: ev.token.id}),
headers: {'content-type': 'application/json'},
})
.then(function(response) {
if (response.ok) {
// Report to the browser that the payment was successful, prompting
// it to close the browser payment interface.
ev.complete('success');
} else {
// Report to the browser that the payment failed, prompting it to
// re-show the payment interface, or show an error message and close
// the payment interface.
ev.complete('fail');
}
});
});
</script>
Server side code in app.js
app.post('/apple-pay', function(req, res, next) {
// Set your secret key: remember to change this to your live secret key in production
// See your keys here: https://dashboard.stripe.com/account/apikeys
var stripe = require("stripe")("sk_test_xxxxxx");
// Token is created using Checkout or Elements!
// Get the payment token ID submitted by the form:
const token = req.body.token; // Using Express
const charge = stripe.charges.create({
amount: 999,
currency: 'usd',
description: 'Example charge',
source: token,
}, function(err, charge){
if (err){
} else {
}
});
});
Finally solved this. It ended up being an issue with my bodyParser set up. This explains why token was being passed though empty. I had neglected to include app.use(bodyParser.json()); below...
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());

Passing Braintree nonce to ruby on rails controller

i am currently using braintree hosted fields, to embed the credit cards into my app. i am looking at how i pass the payment nonce from the view to the controller. the javascript i have seems to be triggering the braintree api and returning a nonce to my alert but i just need to now push this through to the controller to execute the final piece of the code
within the controller create method i have
def create
result = Braintree::PaymentMethod.create(
:customer_id => current_user.customer_cim_id,
:payment_method_nonce => nonce_from_the_client,
:cardholder_name => "#{current_user.first_name} #{current_user.last_name}",
:options => {
:make_default => true,
:fail_on_duplicate_payment_method => true
}
)
new view
- title t('.title')
= form_for(#payment_method, :url => myaccount_payment_methods_path(#payment_method), :html => {:id => 'cardForm'}) do |f|
= render :partial => 'form', :locals => {:f => f}
/ Load Hosted Fields component.
%script{:src => '//js.braintreegateway.com/web/3.0.0-beta.10/js/hosted-fields.min.js'}
form view
.mdl-grid
.panel
%header.panel__header
%h1 Card Payment
.panel__content
.textfield--float-label
%label.hosted-field--label{:for => "card-number"}
%i.material-icons credit_card
Card Number
#card-number.hosted-field
.textfield--float-label
%label.hosted-field--label{:for => "expiration-date"}
%i.material-icons date_range
Expiration Date
#expiration-date.hosted-field
.textfield--float-label
%label.hosted-field--label{:for => "cvv"}
%i.material-icons lock
CVV
#cvv.hosted-field
%footer.panel__footer
= f.submit class: 'pay-button', id: 'button-pay', disabled: true
application.js
var form = document.querySelector('#cardForm');
var submit = document.querySelector('input[type="submit"]');
braintree.client.create({
authorization: 'sandbox_92dswc7q_mbmb637xwpzgxbrd'
}, function (err, clientInstance) {
if (err) {
console.error(err);
return;
}
// Create input fields and add text styles
braintree.hostedFields.create({
client: clientInstance,
styles: {
'input': {
'color': '#282c37',
'font-size': '16px',
'transition': 'color 0.1s',
'line-height': '3'
},
// Style the text of an invalid input
'input.invalid': {
'color': '#E53A40'
},
// placeholder styles need to be individually adjusted
'::-webkit-input-placeholder': {
'color': 'rgba(0,0,0,0.6)'
},
':-moz-placeholder': {
'color': 'rgba(0,0,0,0.6)'
},
'::-moz-placeholder': {
'color': 'rgba(0,0,0,0.6)'
},
':-ms-input-placeholder ': {
'color': 'rgba(0,0,0,0.6)'
}
},
// Add information for individual fields
fields: {
number: {
selector: '#card-number',
placeholder: '1111 1111 1111 1111'
},
cvv: {
selector: '#cvv',
placeholder: '123'
},
expirationDate: {
selector: '#expiration-date',
placeholder: '10 / 2019'
}
}
}, function (err, hostedFieldsInstance) {
if (err) {
console.error(err);
return;
}
hostedFieldsInstance.on('validityChange', function (event) {
// Check if all fields are valid, then show submit button
var formValid = Object.keys(event.fields).every(function (key) {
return event.fields[key].isValid;
});
if (formValid) {
$('.pay-button').prop("disabled", false);
} else {
$('.pay-button').prop("disabled", true);
}
});
hostedFieldsInstance.on('empty', function (event) {
$('header').removeClass('header-slide');
$('#card-image').removeClass();
$(form).removeClass();
});
hostedFieldsInstance.on('cardTypeChange', function (event) {
// Change card bg depending on card type
if (event.cards.length === 1) {
$(form).removeClass().addClass(event.cards[0].type);
$('#card-image').removeClass().addClass(event.cards[0].type);
$('header').addClass('header-slide');
// Change the CVV length for AmericanExpress cards
if (event.cards[0].code.size === 4) {
hostedFieldsInstance.setPlaceholder('cvv', '1234');
}
} else {
hostedFieldsInstance.setPlaceholder('cvv', '123');
}
});
submit.addEventListener('click', function (event) {
event.preventDefault();
hostedFieldsInstance.tokenize(function (err, payload) {
if (err) {
console.error(err);
return;
}
// This is where you would submit payload.nonce to your server
alert('Got a nonce: ' + payload.nonce);
// If this was a real integration, this is where you would
// send the nonce to your server.
console.log('Got a nonce: ' + payload.nonce);
});
}, false);
});
});
Full disclosure: I work at Braintree. If you have any further questions, feel free to contact support.
Right after your alert line in application.js, you will want to send a request to your server that contains the payment method nonce. For example you can do this with Ajax:
$.ajax({
type: "POST",
url: your_payment_url,
data: {"payment_method_nonce":payload.nonce}
});
Then within your Ruby on Rails controller, you can call Transaction.sale using the payment method nonce in the request to complete the transaction.
For more information on hosted fields, please check out this link.
Edit on Vault question:
If you're using Vault, you can charge users without needing a payment nonce each time. After creating the customer (either through the control panel or through Customer.create, you can retrieve a payment_method_token directly through the payment_methods attribute of the Customer object. To charge the user later, retrieve their payment_method_token on your server and call Transaction.sale, passing in the payment_method_token.
result = Braintree::Transaction.sale(
:amount => "your_amount",
:payment_method_token => "payment_method_token"
)

Resources