How to use the Stripe create payment method with mounted element - javascript

I'm integrating Stripe to my Blazor application. The functionality I need is to replace an existing 'Add a card' component.
I have successfully achieved this using the stripe.confirmSetup method in the JS library however this requires a redirect URL which is causing issues as the rest of our application does not behave like that.
I have identified the createPaymentMethod as an alternative due to the fact that it does not require a redirect URL, opens any 3DS page in a modal and supports a 'then' callback method, where I can show a success popup and update the DOM accordingly.
The issue is when I pass the card element that I have previously mounted I get this error.
Invalid value for createPaymentMethod: card was payment Element,
which cannot be used to create card PaymentMethods
razor/html
<EditForm id="payment-form" Model="#BillingProfile">
<div id="payment-element">
<!-- Elements will create form elements here -->
</div>
#if (stripeReady)
{
<div class="text-center mt-2">
<button type="button" #onclick="TogglePaymentEditStripe" class="btn btn-grey text-dark">Cancel</button>
<button type="submit" id="submitStripeCard" disabled="#(Busy)" class="btn btn-blue">Add card</button>
</div> }
<!-- We'll put the error messages in this element -->
<div id="payment-method-errors" role="alert"></div>
</EditForm>
JavaScript
export function initialiseStripeAndMount(publishableKey, clientIntentSecret, redirectUrl){
const stripe = Stripe(publishableKey);
const options = {
clientSecret: clientIntentSecret, //intent
// Fully customizable with appearance API.
appearance: {
theme: 'night',
labels: 'floating',
}
};
// Set up Stripe.js and Elements to use in checkout form, passing the client secret obtained in step 3
const elements = stripe.elements(options);
// Create and mount the Payment Element
const cardElement = elements.create('payment');
cardElement.mount('#payment-element');
// Create a token or display an error when the form is submitted.
const form = document.getElementById('payment-form');
form.addEventListener('submit', async (event) => {
event.preventDefault();
const result = await stripe.createPaymentMethod({
type: 'card',
card: cardElement,
billing_details: {
name: 'Jenny Rosen',
},
})
.then(function (result) {
// Handle result.error or result.paymentMethod
alert(result)
});
});
}
Question
How can I use the 'createPaymentMethod' function with the stripe mounted element so that I can avoid the page posting back as per their documentation:
edit
If I try using confirmSetup with the following:
stripe.confirmSetup({
elements,
confirmParams: {
// Return URL where the customer should be redirected after the SetupIntent is confirmed.
return_url: 'http://www.google.co.uk',
redirect: "if_required"
},
})
Then I get a error code 400 response, with the following response object:
{
"error": {
"code": "parameter_unknown",
"doc_url": "https://stripe.com/docs/error-codes/parameter-unknown",
"message": "Received unknown parameter: redirect",
"param": "redirect",
"request_log_url": "[Redacted]",
"setup_intent": {
"id": "[Redacted]",
"object": "setup_intent",
"cancellation_reason": null,
"client_secret": "[Redacted]",
"created": [Redacted],
"description": null,
"last_setup_error": null,
"livemode": false,
"next_action": null,
"payment_method": null,
"payment_method_types": [
"card"
],
"status": "requires_payment_method",
"usage": "off_session"
},
"type": "invalid_request_error"
}
}

If you are only accepting card payments, then it's possible to have no redirects when using SetupIntents. Just add redirect: "if_required" when calling stripe.confirmSetup(). Learn more. It should look something like this:
stripe.confirmSetup({
elements,
redirect: "if_required",
confirmParams: { return_url: 'http://foo.bar' }
})
For your specific error message, I think the issue is that you are passing Card: cardElement (uppercase C) instead of card: cardElement (lowercase c). But note that createPaymentMethod will only work with the Card Element (and not the Payment Element).

Related

Vue3 JS - How to access/print values stored in an object onto document <template>

New to Vue, JS and Firebase been taking tutorials for weeks and now trying to build a test project. Embarrassed to say I have been stuck on this particular problem for several days. Trying to write this question as best I can given my current level of understanding which is pretty basic.
Desired Outcome: What I am trying to do is query firebase database to get the last event created by current user so that, if it exists, I can then pre-fill some of the Vue form data when they create their next entry.
Sounds simple enough but my tutorials only cover single document retrieval/display when you already have the document id, OR, how to retrieve and display all documents from a collection in a list. My need is hybrid in that don't know the document ID until I query to get it and every attempt to combine the functionality from tutorials has failed...multiple times.
Currently, I am able to query the DB just fine and can verify it returns the results I want to an object which I can log to the console and 'see', but, can't for the life of me figure out how to get to the data within that object once back on the doc template.
JS Code (getLastEvent-SO.js)
import { ref } from 'vue'
import { db } from '#/firebase/config'
import { collection, getDocs, query, where, orderBy, limit } from 'firebase/firestore'
const getLastEvent = (u) => { // u = user id
const lastEvent = ref([])
let collectionRef = collection(db, 'events') // establish reference
collectionRef = query(collectionRef, where("userId", "==", u ), orderBy("created", "desc"), limit(1)) // should return last event if exists
getDocs(collectionRef)
.then(snapshot => {
let docs = []
let eId = null
snapshot.docs.forEach(doc => { // using a loop because I couldnt figure out how to get a single doc without first having the id of that doc which I don't know until I query to get users last event created
docs.push({ ...doc.data(), id: doc.id })
})
lastEvent.value = docs // assign value of docs to lastEvent
})
console.log('lastEvent1:', lastEvent) // console display proves the record stored is the one I want but how do I access/print to console the values?
return { lastEvent }
}
export default getLastEvent
Vue Code:
<template>
<form #submit.prevent="handleSubmit">
<h2>New event</h2>
<p v-if="lastEvent">Last Start Date: {{ lastEvent.startDate }} </p> <!-- PROBLEM: this will NOT display - why??? -->
...more form inputs...
<button>Add event</button>
</form>
</template>
<script>
import getUser from "#/composables/getUser";
import getLastEvent from "#/composables/getLastEvent-SO";
export default {
setup() {
// Query last user event created so default form values based on last entry can be set
const { user } = getUser();
const { lastEvent } = getLastEvent(user.value.uid)
return { lastEvent };
},
};
</script>
Copied from Chrome/console (lastEvent):
{
"__v_isShallow": false,
"dep": {
"w": 0,
"n": 0
},
"__v_isRef": true,
"_rawValue": [
{
"userId": "H0jZIKGTggeX4DsjucGR50Cb8663",
"reservation": "",
"startDate": "2022-07-07",
"created": 1656595528420,
"location": "mpmCHrVYDK9icr3yTnYG",
"startTime": "09:00a",
"type": "Open",
"duration": 1,
"id": "ZHnbrLWkYO9gHdjKiygk"
}
],
"_value": [
{
"userId": "H0jZIKGTggeX4DsjucGR50Cb8663",
"reservation": "",
"startDate": "2022-07-07",
"created": 1656595528420,
"location": "mpmCHrVYDK9icr3yTnYG",
"startTime": "09:00a",
"type": "Open",
"duration": 1,
"id": "ZHnbrLWkYO9gHdjKiygk"
}
]
}
Any hint as to why I am unable to access the data from within the document template as {{ lastEvent.startDate }} ??
[edit: added this screen-shot of the console where I copied the object from by request]
Screen shot of console
Aside from getLastEvent being asynchronous (make sure you await your database call before returning lastEvent), what you're returning from the function is an array, while in your Vue file you're using it as if it's an object. Either set lastEvent.value equal to desired element in your array (preferred)
const snapshot = await getDocs(collectionRef);
...
lastEvent.value = docs[0]; // assign value of docs to lastEvent
or in your Vue file you can call (though you still need to make your function asynchronous):
<p v-if="lastEvent">Last Start Date: {{ lastEvent[0].startDate }}</p>

React PayPal Checkout

I built a small and simple webshop with a PayPal checkout and it works so far. Know I want to see in the transaction history of my PayPal account the product which was purchased. So I need to add more details about the purchase to see what order was placed.
I found multiple tutorials but I still don't know how to implement this. This is my payPalButton component so far:
How can I add more details to the payment object??
import React from 'react';
import PaypalExpressBtn from 'react-paypal-express-checkout';
export default class MyApp extends React.Component {
render() {
const onSuccess = (payment) => {
payment.console // Congratulation, it came here means everything's fine!
.log('The payment was succeeded!', payment);
this.props.clearCart();
this.props.history.push('/');
// You can bind the "payment" object's value to your state or props or whatever here, please see below for sample returned data
};
const onCancel = (data) => {
// User pressed "cancel" or close Paypal's popup!
console.log('The payment was cancelled!', data);
// You can bind the "data" object's value to your state or props or whatever here, please see below for sample returned data
};
const onError = (err) => {
// The main Paypal's script cannot be loaded or somethings block the loading of that script!
console.log('Error!', err);
// Because the Paypal's main script is loaded asynchronously from "https://www.paypalobjects.com/api/checkout.js"
// => sometimes it may take about 0.5 second for everything to get set, or for the button to appear
};
let env = 'sandbox'; // you can set here to 'production' for production
let currency = 'EUR'; // or you can set this value from your props or state
// let total = 1; // same as above, this is the total amount (based on currency) to be paid by using Paypal express checkout
// Document on Paypal's currency code: https://developer.paypal.com/docs/classic/api/currency_codes/
const client = {
sandbox: process.env.REACT_APP_ID,
production: 'YOUR-PRODUCTION-APP-ID',
};
// In order to get production's app-ID, you will have to send your app to Paypal for approval first
// For sandbox app-ID (after logging into your developer account, please locate the "REST API apps" section, click "Create App"):
// => https://developer.paypal.com/docs/classic/lifecycle/sb_credentials/
// For production app-ID:
// => https://developer.paypal.com/docs/classic/lifecycle/goingLive/
// NB. You can also have many Paypal express checkout buttons on page, just pass in the correct amount and they will work!
return (
<PaypalExpressBtn
env={env}
client={client}
currency={currency}
total={this.props.total}
onError={onError}
onSuccess={onSuccess}
onCancel={onCancel}
style={{
size: 'small',
color: 'blue',
shape: 'rect',
}}
/>
);
}
}
You may be using an older react component, try https://www.npmjs.com/package/react-paypal-button-v2
Don't use onSuccess, switch to onApprove: https://stackoverflow.com/a/62193541/2069605
Here is an example of a v2/orders purchase_units object with a description and two items:
"purchase_units": [{
"description": "Stuff",
"amount": {
"value": "20.00",
"currency_code": "USD",
"breakdown": {
"item_total": {
"currency_code": "USD",
"value": "20.00"
},
}
},
"items": [
{
"unit_amount": {
"currency_code": "USD",
"value": "10.00"
},
"quantity": "1",
"name": "Item 1",
},
{
"unit_amount": {
"currency_code": "USD",
"value": "10.00"
},
"quantity": "1",
"name": "Item 2",
},
],
}]
(A lot of those fields are actually required and the totals must match up, so it's very useful to have this sample as a starting point.)

Stripe business profile is not showing on request body

I'm in test mode, I create a custom connected account successfully using JavaScript in frontend and PHP in backend.
The account created successfully but for some reason, the business profile is not showing on request body (I see this in stripe log in dashboard).
I see a warning message before form submition:
business_profile is not a recognized parameter
For your reference, here’s the API coding I used when I did my test:
https://stripe.com/docs/api/accounts/create?lang=php#create_account-business_profile
JavaScript
const accountResult = await stripe.createToken('account', {
business_type: 'company',
company: {...},
business_profile: {
mcc: "5812",
support_email: "test#example.com",
url: "https://example.com",
},
tos_shown_and_accepted: true,
});
PHP
// ...
$account = \Stripe\Account::create([
"country" => "FR",
"type" => "custom",
"account_token" => $account_token,
]);
// ...
stripe.createToken doesn't take a business_profile value, nor does it manage Stripe\Account objects at all - it creates a Stripe\Token. You'll have to update that information via a separate call to the Stripe API. The parameters it does take are documented here:
name, address_line1,address_line2, address_city, address_state, address_zip, address_country, currency

Razor Pay checkout form not appearing and giving error as not a function

I am implementing razorPay payment in angular7 project.I am trying to open checkout form by creating a single instance of Razorpay and accessing its function open(). But instead it is throwing error "is not a function". I couldn't find any proper documentation regarding this.
However, If I try to simple create payment using razorpay, it opens up the window but there is no option for user to choose any method and hence it throws error again because directly creating payment will not add up any card(if method is 'card'). So it is necessary to open checkout form.
Here is my function I am using, firstly i create order on server to generate id and then proceed to payment.
buyPlan() {
let postdata = {
amount: 1000,
currency: "INR",
receipt: "1",
notes: {},
payment_capture: true
}
this.common.createOrder(postdata).subscribe(
(result: any) => {
console.log(result)
var razorpay = new this.winRef.nativeWindow.Razorpay({
key: 'dashboard_key_id',
image: 'assets/images/logo-1.png',
});
var data = {
amount: 1000,
currency: "INR",
email: 'abc#example.com',
contact: '9874563210',
notes: {
address: 'Sector 65 Delhi',
},
method: 'card',
order_id: result.data.id,
handler: function (response) {
alert(response.razorpay_payment_id);
}
};
razorpay.open();
}, (err: HttpErrorResponse) => {
}
);
}
For Custom Integration
Remove:
<script src="https://checkout.razorpay.com/v1/checkout.js"></script>
from index.html file
and put:
<script type="text/javascript" src="https://checkout.razorpay.com/v1/razorpay.js"></script>
inside <head></head> tag

Creating an envelope from a template returning "UNSPECIFIED_ERROR"

When I try to create an envelope from a template I get a response of:
{ errorCode: 'UNSPECIFIED_ERROR',
message: 'Non-static method requires a target.' }
Here's what I'm doing so far:
First I login, which returns
{ loginAccounts:
[ { name: '*****',
accountId: '*****',
baseUrl: 'https://demo.docusign.net/restapi/v2/accounts/******',
isDefault: 'true',
userName: '***** ********',
userId: '*******-*****-*****-*****-*********',
email: '********#*******.com',
siteDescription: '' } ] }
So then I take the baseUrl out of that response and I attempt to create the envelope. I'm using the hapi framework and async.waterfall of the async library, so for anyone unfamiliar with either of these my use of the async library uses the next callback to call the next function which in this case would be to get the url for the iframe, and with our usage of the hapi framework AppServer.Wreck is roughy equivalent to request:
function prepareEnvelope(baseUrl, next) {
var createEntitlementTemplateId = "99C44F50-2C97-4074-896B-2454969CAEF7";
var getEnvelopeUrl = baseUrl + "/envelopes";
var options = {
headers: {
"X-DocuSign-Authentication": JSON.stringify(authHeader),
"Content-Type": "application/json",
"Accept": "application/json",
"Content-Disposition": "form-data"
},
body : JSON.stringify({
status: "sent",
emailSubject: "Test email subject",
emailBlurb: "My email blurb",
templateId: createEntitlementTemplateId,
templateRoles: [
{
email: "anemailaddress#gmail.com",
name: "Recipient Name",
roleName: "Signer1",
clientUserId: "1099", // TODO: replace with the user's id
tabs : {
textTabs : [
{
tabLabel : "acct_nmbr",
value : "123456"
},
{
tabLabel : "hm_phn_nmbr",
value : "8005882300"
},
{
tabLabel : "nm",
value : "Mr Foo Bar"
}
]
}
}
]
})
};
console.log("--------> options: ", options); // REMOVE THIS ====
AppServer.Wreck.post(getEnvelopeUrl, options, function(err, res, body) {
console.log("Request Envelope Result: \r\n", JSON.parse(body));
next(null, body, baseUrl);
});
}
And what I get back is:
{ errorCode: 'UNSPECIFIED_ERROR',
message: 'Non-static method requires a target.' }
From a little googling it look like 'Non-static method requires a target.' is a C# error and doesn't really give me much indication of what part of my configuration object is wrong.
I've tried a simpler version of this call stripping out all of the tabs and clientUserId and I get the same response.
I created my template on the Docusign website and I haven't ruled out that something is set up incorrectly there. I created a template, confirmed that Docusign noticed the named form fields, and created a 'placeholder' templateRole.
Here's the templateRole placeholder:
Here's one of the named fields that I want to populate and corresponding data label:
As a side note, I was able to get the basic vanilla example working without named fields nor using a template using the docusign node package just fine but I didn't see any way to use tabs with named form fields with the library and decided that I'd rather have more fine-grained control over what I'm doing anyway and so I opted for just hitting the APIs.
Surprisingly when I search SO for the errorCode and message I'm getting I could only find one post without a resolution :/
Of course any help will be greatly appreciated. Please don't hesitate to let me know if you need any additional information.
Once I received feedback from Docusign that my api call had an empty body it didn't take but a couple minutes for me to realize that the issue was my options object containing a body property rather than a payload property, as is done in the hapi framework.

Categories

Resources