Adding Stripe Subscription to Blazor WASM - stripe-payments

I am trying to add Stripe Subscription to my Blazor WASM Application following these instructions Since they are using JavaScript I am using the JavaScript interop. I added Stripe's script to my index.html and added a custom script with the javascript they have in the instructions.
Index.html inside the <head> tag
<script src="https://js.stripe.com/v3/"></script>
<script src="stripescript.js"></script>
stripescript.js:
let stripe = window.Stripe('MY PUBLIC KEY');
let elements = stripe.elements();
let card = elements.create('card', { style: style });
card.mount('#card-element');
card.on('change', function (event) {
displayError(event);
});
function displayError(event) {
changeLoadingStatePrices(false);
let displayError = document.getElementById('card-element-errors');
if (event.error) {
displayError.textContent = event.error.message;
} else {
displayError.textContent = '';
}
}
function createPaymentMethod(cardElement, customerId, priceId) {
return stripe
.createPaymentMethod({
type: 'card',
card: cardElement,
})
.then((result) => {
if (result.error) {
displayError(error);
} else {
//change this to call .net
createSubscription({
customerId: customerId,
paymentMethodId: result.paymentMethod.id,
priceId: priceId,
});
}
});
}
My assumption is that the variable initializations would happen when the application is loaded. However, when I add the following HTML to my Razor page is not populating the card component.
<form id="payment-form">
<div id="card-element">
<!-- Elements will create input elements here -->
</div>
<!-- We'll put the error messages in this element -->
<div id="card-element-errors" role="alert"></div>
<button type="submit">Subscribe</button>
</form>
I am lost on how to debug this, or if this is even possible in Blazor.

Thanks to #Umair's Comments, I realized that I had made a few mistakes and some of them were showing up in the console since I was trying to initialize the card element before the DOM was loaded. I was able to fix my problem by first changing the card mount into its own function. Here is the full stripescript.js for future people that have this problem:
let stripe = window.Stripe('MY KEY');
let elements = stripe.elements();
let style = {
base: {
fontSize: '16px',
color: '#32325d',
fontFamily:
'-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif',
fontSmoothing: 'antialiased',
'::placeholder': {
color: '#a0aec0',
},
},
};
let card = elements.create('card', { style: style });
function mountCard() {
card.mount('#card-element');
}
card.on('change', function (event) {
displayError(event);
});
function displayError(event) {
changeLoadingStatePrices(false);
let displayError = document.getElementById('card-element-errors');
if (event.error) {
displayError.textContent = event.error.message;
} else {
displayError.textContent = '';
}
}
function createPaymentMethod(cardElement, customerId, priceId) {
return stripe
.createPaymentMethod({
type: 'card',
card: cardElement,
})
.then((result) => {
if (result.error) {
displayError(error);
} else {
//todo change this to call .net
createSubscription({
customerId: customerId,
paymentMethodId: result.paymentMethod.id,
priceId: priceId,
});
}
});
}
and added the following C# code to my Blazor component to render the card:
[Inject] IJSRuntime js { get; set; }
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await js.InvokeVoidAsync("mountCard");
}

I found a solution for this on Youtube that is super complicated, and I decided to create my own implementation following KISS.
I have streamlined youtube's implementation on a single StripeCard component that interacts with my custom Js StripeInterop. I hope this makes your life easier.
This will allow you to have different publishable keys for your different environments without hardcoding it and reuse the component in multiple pages if you wish. Also, it will destroy itself when the component is used.
Here is my solution for (blazor webassembly).
Add this to index.html
<!-- Stripe -->
<script src="https://js.stripe.com/v3/"></script>
<script src="stripescript.js"></script>
Here is stripescript.js
StripeInterop = (() => {
var stripe = null;
var elements = null;
var dotNetReference = null;
var card = null;
var style = {
base: {
color: '#32325d',
fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
fontSmoothing: 'antialiased',
fontSize: '16px',
'::placeholder': {
color: '#aab7c4'
}
},
invalid: {
color: '#fa755a',
iconColor: '#fa755a'
}
};
return {
init(dotnetHelper, publishableKey) {
stripe = window.Stripe(publishableKey);
elements = stripe.elements();
card = elements.create('card', { style: style });
dotNetReference = dotnetHelper;
card.mount('#card-element');
card.on('change', function (event) {
displayError(event);
});
},
createPaymentMethod(billingEmail, billingName) {
return stripe
.createPaymentMethod({
type: 'card',
card: card,
billing_details: {
name: billingName,
email: billingEmail
}
})
.then(function (result) {
if (result.error) {
displayError(result);
} else {
dotNetReference.invokeMethodAsync('ProcessPaymentMethod', result.paymentMethod.id);
}
});
},
destroy() {
dotNetReference.dispose();
card.destroy();
}
};
function displayError(event) {
var showError = document.getElementById('card-element-errors');
if (event.error) {
showError.textContent = event.error.message;
} else {
showError.textContent = '';
}
}
})();
Here is StripeCard.razor component
#namespace SmartApp.Components
<div id="card-element" style="display: block;
width: 100%;
padding: 0.52rem .75rem;
font-size: 1rem;
line-height: 1.5;
color: #495057;
background-color: #fff;
background-clip: padding-box;
border: 1px solid #ced4da;
border-radius: .25rem;
transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out;">
</div>
<div id="card-element-errors" class="validation-message"></div>
Here is StripeCard.razor.cs
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
namespace SmartApp.Components
{
public partial class StripeCard : IDisposable
{
[Inject] IJSRuntime JS { get; set; }
[Parameter] public string PublishableKey { get; set; }
[Parameter] public EventCallback<string> CardProcessedCallBack { get; set; }
private bool _firstTime;
protected override async Task OnInitializedAsync()
{
_firstTime = true;
await base.OnInitializedAsync();
}
public async void Dispose()
{
await JS.InvokeVoidAsync("StripeInterop.destroy");
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (_firstTime)
{
_firstTime = false;
await JS.InvokeVoidAsync("StripeInterop.init", DotNetObjectReference.Create(this), PublishableKey);
}
}
[JSInvokable("ProcessPaymentMethod")]
public Task ProcessPaymentMethod(string paymentId)
{
return CardProcessedCallBack.InvokeAsync(paymentId);
}
}
}
Here is how we use StripeCard.razor in our Page.
#page "/subscription/payment"
#attribute [Authorize(Roles = "Admin,Organisation")]
#inject IJSRuntime JS
<div class="card card-custom card-shadowless rounded-top-0">
<div class="card-body p-0">
<div class="row justify-content-center py-8 px-8 py-lg-15 px-lg-10">
<div class="col-xl-12 col-xxl-7">
<!--begin: Wizard Form-->
<EditForm Model="#_model" OnValidSubmit="HandleValidSubmit" >
<DataAnnotationsValidator />
<h4 class="mb-10 font-weight-bold text-dark">Enter your Payment Details</h4>
<div class="row">
<div class="col-xl-6">
<!--begin::Input-->
<div class="form-group">
<label>Name on Card</label>
<InputText #bind-Value="_model.BillingName" name="ccname" class="form-control form-control-solid form-control-lg"
placeholder="Jane Doe" />
<ValidationMessage For="#(() => _model.BillingName)" />
</div>
<!--end::Input-->
</div>
<div class="col-xl-6">
<!--begin::Input-->
<div class="form-group">
<label>Notification Email</label>
<InputText #bind-Value="_model.BillingEmail" name="ccemail" class="form-control form-control-solid form-control-lg"
placeholder="jane.doe#domain.com" />
<ValidationMessage For="#(() => _model.BillingEmail)" />
</div>
<!--end::Input-->
</div>
</div>
<div class="row">
<div class="col-xl-12">
<!--begin::Input-->
<div class="form-group">
<label>Card Information</label>
<StripeCard PublishableKey="#_stripePublishableKey" CardProcessedCallBack="ProcessSubscriptionAsync"></StripeCard>
<span class="form-text text-muted">Powered by <strong>Stripe</strong>.</span>
</div>
<!--end::Input-->
</div>
</div>
<ValidationSummary />
<div class="d-flex justify-content-between border-top mt-5 pt-10">
<div>
<button type="button" class="btn btn-success font-weight-bolder text-uppercase px-9 py-4"
#onclick="HandleValidSubmit">
Submit
</button>
</div>
</div>
</EditForm>
<!--end: Wizard Form-->
</div>
</div>
</div>
</div>
#code{
private Model _model;
private string _stripePublishableKey;
protected override void OnInitialized()
{
_model = new Model();
_stripePublishableKey = "Here we put Development or Production publishableKey"; //Add publishable key here
base.OnInitialized();
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await JS.InvokeVoidAsync("KTSubscriptionCheckout.init");
}
await base.OnAfterRenderAsync(firstRender);
}
private async Task HandleValidSubmit()
{
await JS.InvokeVoidAsync("StripeInterop.createPaymentMethod", _model.BillingEmail, _model.BillingName);
}
//Callback method will return stripe paymentId
private async Task ProcessSubscriptionAsync(string paymentId)
{
//We process paymentId here and continue with our backend process
await Task.CompletedTask;
}
public class Model
{
[Required]
public string BillingName { get; set; }
[Required]
public string BillingEmail { get; set; }
}
}
Youtube video if you want to have a look. https://youtu.be/ANYvFHHfyy8
Good luck...

Related

Lit-element children component not re rendering JS code based on updated props

Lit-Element updated .props not invoking full re-render of child component, i.e. the javascript code inside firstUpdated() of child constructs a leaflet map based on .props being passed in from parent component, when the parent component updates location and city, it doesn't create a re-render of the map with new location and city.
When user clicks on button to update location from Seattle to Toronto, the parents props are updated and passed to child, however, the map doesn't rebuild itself, how do I force the map to be "rebuilt" (re-rendered) based on the new .props being passed into the child ??
Git repo for my working sample code
THANKS! :) Been struggling with this for days on end - FYI new to Lit-Element.
Index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link type="text/css" href="./styles.css"
<script src="./webcomponents/webcomponents-loader.js"></script>
<script>
if (!window.customElements) { document.write('<!--'); }
</script>
<script src="./webcomponents/custom-elements-es5-adapter.js"></script>
<!-- ! DO NOT REMOVE THIS COMMENT, WE NEED ITS CLOSING MARKERS -->
</head>
<body>
<app-view></app-view>
</body>
</html>
Index.js:
import './views/app-view.js'
Parent component app-view.js
import { html, LitElement } from 'lit-element';
import './esri-map.js'
class AppView extends LitElement{
static get properties() {
return {
location: { type: Object },
city: { type: String }
}
}
constructor() {
super();
// set fallback location to seattle -- GET will pull in coordinates for Toronto
this.location = { lat: "47.608013", long: "-122.335167" }
this.city = "Seattle"
}
render() {
return html`
<style>
#app-container{
width: 100%,;
height: 100%;
display: flex;
}
#map-container{
flex-grow: 1;
height: 800px;
}
</style>
<button #click=${ (event) => this.updateLocation() }
>Set to Toronto
</button>
<div id="app-container">
<div id="map-container">
<esri-map
.location=${this.location}
.city=${this.city}
>
</esri-map>
</div>
</div>
`;
}
updateLocation(){
var oldLocation = this.location;
this.location = { lat: "43.651070", long: "-79.347015"} // Set to Toronto
this.city = "Toronto"; // Set to Toronto
console.log("New location is: " + this.city)
console.log("Coordinates: ");
console.log(this.location);
}
}
customElements.define('app-view', AppView);
Child Component
import { html, LitElement } from 'lit-element';
import * as L from 'leaflet';
import * as esri from 'esri-leaflet';
class EsriMap extends LitElement{
static get properties() {
return {
location: { type: Object }, // prop passed from parent
city: { type: String } // prop passed from parent
}
}
render() {
return html`
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.6.0/dist/leaflet.css"/>
<style>
#map{
height: 100%;
width: 100%;
}
</style>
<h2>Current City: ${this.city} </h2>
<h3>Coordinates: Lat: ${this.location.lat}</h3>
<div id="map"></div>
`
}
firstUpdated() {
const mapNode = this.shadowRoot.querySelector('#map');
// Render map with props from parent component
var map = L.map(mapNode, {
maxZoom: 18,
minZoom: 2,
}).setView([this.location.lat, this.location.long],8); // [Lat,Lng]
const esriLayer = esri.basemapLayer('Streets');
map.addLayer(esriLayer);
// Render circle with props from parent component
var circle = L.circle([this.location.lat, this.location.long], {
color: 'red',
fillColor: '#f03',
fillOpacity: 0.5,
radius: 20000,
}).addTo(map);
}
}
customElements.define('esri-map', EsriMap);
When properties change, the render() function gets invoked.
firstUpdated() gets only invoked after the first update and not on every property change.
Try this:
updated(changedProps) {
if (changedProps.has('location')) {
this._setMap();
}
}
_setMap() {
const mapNode = this.shadowRoot.querySelector('#map');
if (mapNode == null || this.location == null) {
return;
}
// Render map with props from parent component
var map = L.map(mapNode, {
maxZoom: 18,
minZoom: 2,
}).setView([this.location.lat, this.location.long],8); // [Lat,Lng]
const esriLayer = esri.basemapLayer('Streets');
map.addLayer(esriLayer);
// Render circle with props from parent component
var circle = L.circle([this.location.lat, this.location.long], {
color: 'red',
fillColor: '#f03',
fillOpacity: 0.5,
radius: 20000,
}).addTo(map);
}

react-virtualized: Table Column with CellMeasurer always has cache height

I've been trying to follow the DynamicHeightTableColumn example and adapt it for my use case. I can see in the CellMeasurer demo that the single-line rows have the default height, defined when initializing the cache. However, in my case, it seems like single-line rows always have the tallest row height, stored in the cache, instead of the default one.
Here is my code:
import * as React from 'react';
import * as Immutable from 'immutable';
import { Table, Column, AutoSizer, CellMeasurer, CellMeasurerCache } from 'react-virtualized';
interface Props {
logs: Immutable.List<Immutable.Map<string, any>>;
columns: (
width: number
) => Array<{
name: string;
key: string;
width: number;
variableHeight?: boolean;
}>;
}
export default class LogTable extends React.Component<Props, {}> {
private cache: CellMeasurerCache;
constructor(props) {
super(props);
this.cache = new CellMeasurerCache({
defaultHeight: 20,
fixedWidth: true,
keyMapper: () => 1
});
}
render() {
const { logs } = this.props;
return (
<AutoSizer disableHeight>
{({ width }) => (
<Table
headerHeight={20}
height={250}
rowCount={logs.size}
rowHeight={this.cache.rowHeight}
width={width}
overscanRowCount={2}
rowRenderer={this.rowRenderer}
headerClassName='col-header'
gridClassName='log-table-grid'
rowClassName='log-table-row'
className='log-table'
rowGetter={({ index }) => logs.get(index)}
deferredMeasurementCache={this.cache}>
{this.renderColumns(width)}
</Table>
)}
</AutoSizer>
);
}
private renderColumns(width) {
return this.props.columns(width).map(({ name, key, width, variableHeight }, idx) => {
const props: any = {
label: name,
dataKey: key,
width,
key,
className: 'column'
};
if (variableHeight) {
props.cellRenderer = this.cellRenderer.bind(this, idx);
}
return <Column {...props} />;
});
}
private rowRenderer(params) {
const { key, className, columns, rowData, style } = params;
if (!rowData) {
return null;
}
return (
<div className={className} key={key} style={style}>
{columns}
</div>
);
}
private cellRenderer(colIndex, { dataKey, parent, rowIndex }) {
const content = this.props.logs.get(rowIndex).get(dataKey);
return (
<CellMeasurer
cache={this.cache}
columnIndex={colIndex}
key={dataKey}
parent={parent}
rowIndex={rowIndex}>
<div style={{ whiteSpace: 'normal' }} title={content}>
{content}
</div>
</CellMeasurer>
);
}
}
And this is the output (see 2nd row in the table that's too tall for its content)
The only styling (less) I have is the following, which I don't think can cause this behavior
.log-table {
font-size: 14px;
.col-header {
font-size: 15px;
text-align: center;
}
.log-table-grid {
outline: none;
font-family: monospace;
}
.log-table-row {
border-bottom: 1px solid gray;
padding: 3px;
}
}

Moving single characters from a certain position to another position automatically

Which library in React to create animation of moving a single char from a string to a new position automatically?
This is an implementation without any library:
class AnimateChar extends React.Component {
animateChar = () => {
const { children, charIndex } = this.props;
return (
children.split('').map((char, i) => (
<span className={i === charIndex ? 'animate' : ''}>
{char}
</span>
))
)
}
render() {
return this.animateChar();
}
}
ReactDOM.render(<AnimateChar charIndex={2}>moveme</AnimateChar>, document.getElementById('root'))
#keyframes move {
0% {transform: translateY(0)}
100% {transform: translateY(12px)}
}
.animate {
display: inline-block;
animation: move .5s ease-in-out forwards
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

AUI datepicker: pop up datepicker on focus of element

How can i make AUI-datepicker to pop up on the focus of element. cuurrently it only pop up on click of element.
Here is code
Script:
YUI().use('aui-datepicker',
function(Y) {
new Y.DatePicker(
{
trigger: '.date-selector',
popover: {
zIndex: 1
},
}
);
}
);
and Tag
<aui:input id="startDate" name="startDate" cssClass="date-selector" label="startDate">
and one more thing how can i range date?
Try this something like this:
<aui:input name="taskStartDate" autocomplete="off" cssClass='font-size' id="taskStartDate" onFocus="onClickOfStartDate();" required="true" inlineLabel="true" label=" "/>
<aui:script>
function setactualStartDate(){
AUI().use('aui-datepicker', function(A) {
var simpleDatepicker1 = new A.DatePicker({
trigger: '#<portlet:namespace />taskActualStartDate',
mask: '%Y-%m-%d',
calendar: {
dateFormat: '%Y-%m-%d',
},
}).render('#<portlet:namespace />taskactualStartDatePicker');
});
}
function onClickOfStartDate(){
setStartDate();
}
</aui:script>
The Datepicker popup is handled by DatePickerPopover class of aui-datepicker module. There is show() method in datepicker class to open popup.
<input id="startDate" name="startDate" class="date-selector" onfocus="openDatePicker();">
<script>
var datePicker;
YUI().use('aui-base','aui-datepicker', function(Y) {
datePicker = new Y.DatePicker({
trigger: '#startDate',
popover: {
zIndex: 10,
},
calendar: {
maximumDate: new Date()
}
});
});
function openDatePicker() {
datePicker.getPopover().show();
}
</script>
Date can be ranged by adding maximumDate and minimumDate attribute.

How to open camera in winJs

i am working for a face detection mechanism in winJs starting from the basic. What is the mechanism to open a Camera in winJs and in which tag to show the video.
This is the code i know till now
var Capture = Windows.Media.Capture;
var mediaCapture = new Capture.MediaCapture();
mediaCapture.initializeAsync();
How to show in a Div the same.
here's the html for the same.
function init() {
livePreview = document.getElementById("live-preview");
startCamera();
}
function startCamera() {
try {
mediaCapture = new Capture.MediaCapture();
mediaCapture.initializeAsync().then(function () {
livePreview.src = URL.createObjectURL(mediaCapture);
livePreview.play();
});
} catch(exception) {
Windows.UI.Popups.MessageDialog(exception.message, "Error").showAsync();
}
}
HTML
<div id="application" style="width:100%; height: 180px; overflow: hidden; background: #222;">
<video id="live-preview" style="display : none; width:100%; height: 180px; overflow: hidden;"></video>
</div>
these were some of the variables Select appropriate ones
var Capture = Windows.Media.Capture;
// Globals
var mediaCapture;
var recording = false;
var livePreview;
var activation = Windows.ApplicationModel.Activation;

Resources