Nativescript stripe plugin expire date of the card - nativescript

I'm using the plugin NativeScript Stripe(https://github.com/triniwiz/nativescript-stripe) with Angular. When i get the data dynamically from the frontend, the card number and the cvv are fine, but the expiring month and year are failing the card validation process. When I replace the dynamic month and year data with hardcoded values everything works great. Here is my code:
import { Component, OnInit, ViewChild, ElementRef } from
"#angular/core";
import { Router } from '#angular/router';
import {Page} from "ui/page";
import {View} from "ui/core/view";
import { CreditCardView, Card, Stripe } from 'nativescript-stripe';
import { EventData } from 'data/observable';
#Component({
selector: "Payment",
moduleId: module.id,
templateUrl: "./payment.component.html"
})
export class PaymentComponent implements OnInit {
#ViewChild("card") card: ElementRef;
constructor(page: Page) {}
ngOnInit(): void {
// Init your component properties here.
}
submit(args: EventData): void {
let cardView = <CreditCardView>this.card.nativeElement;
let card = <Card>cardView.card.card;
const stripe = new Stripe('...');
stripe.createToken(card,(error,token)=>{
if (!error) {
console.log(token.getId());
console.log(token.getCard());
} else {
console.log(error);
}
});
}
HTML:
<CreditCardView id="card"></CreditCardView>
<Button text="Button" (tap)="submit($event)"></Button>
When I try to print the card properties, I get the right card number and cvv, but both card.expireMonth and card.expireYear are giving me a 10 digits number, not a 2 digits one. I'm not sure if i did something wrong, or this is a bug. I've tested only on Android.
Edit:
This is another way that I tried that I think shows the problem better. card.expMonth and card.expYear values are 10 digits numbers, while card.number and card.cvc are the ones from the user input. If i replace the month and year with hardcoded two digit values, everything works perfect.
submit(args: EventData): void {
let cardView = <CreditCardView>this.card.nativeElement;
let card = cardView.card;
const stripe = new Stripe('...');
const cc:Card = new Card(card.number, card.expMonth, card.expYear, card.cvc);
stripe.createToken(cc.card,(error,token)=>{
if (!error) {
console.log(token.getId());
console.log(token.getCard());
} else {
console.log(error);
}
});
}
}
Best regards,
Vlad

Related

React.js using context api to implement the dark/light theme, is it possible to get data in App component when I am using contextprovider?

I am using the context api to give my application the toggling ability between the dark/light mode, I managed to toggle the mode in all the children components of App component but when I tried to implement it to the component itself I failed I guess this related the fact the I am using the contextProvider within this component, code below for :
import React from 'react'
import styles from './App.module.css'
import { Card, CountryPicker, Chart } from './components/index'
import { fetchData } from './api/index'
import ThemeContextProvider, { ThemeContext } from './contexts/ThemeContext'
import ToggleTheme from './components/ToggleTheme/ToggleTheme'
export default class App extends React.Component {
static contextType = ThemeContext
state = {
data: {},
country: '',
}
handleCountryChange = async (country) => {
// console.log(country)
const Data = await fetchData(country)
// console.log(Data)
this.setState({ data: Data, country })
// console.log(this.state.data, this.state.country)
}
async componentDidMount() {
const data = await fetchData();
this.setState({ data })
}
render() {
const { data, country } = this.state;
// problem here
const { isLightTheme, dark, light } = this.context;
return (
<ThemeContextProvider>
<div className={styles.container} >
<ToggleTheme />
<Card data={data} />
<CountryPicker handleCountryChange={this.handleCountryChange} />
<Chart data={data} country={country} />
</div>
</ThemeContextProvider>
)
}
}
I figured it out,the solution was very simple, just importing the App component into other componentMain and wrapping it with <ContextProvider></ContextProvider> and import in the index.js

Nativescript TabView using to much space when adding title text

Is it possible to remove some of the padding inside a tabview item?
The TabView has a lot of empty unused space. If I could remove this then my tabs wouldn't get cutoff. This would greatly improve my app since now you have to swipe a bit to the right to see the full title of the last menu item.
The tabview is created in NS-Vue but I don't think that will matter since this is a native issue.
I'm using negative margins to remove padding from TabView items. That was the only solution I found to do this. This approach is needed only for Android, for iOS it works fine.
import { Component, OnInit, HostListener } from '#angular/core';
import { Page } from 'tns-core-modules/ui/page/page';
import { RouterExtensions } from 'nativescript-angular/router';
import { ActivatedRoute } from '#angular/router';
import { TabView } from 'tns-core-modules/ui/tab-view';
import * as platform from 'tns-core-modules/platform';
#Component({
selector: 'app-tabs',
moduleId: module.id,
templateUrl: './tabs.component.html'
})
export class TabsComponent implements OnInit {
tabView: TabView;
constructor(
private page: Page,
private router: RouterExtensions,
private route: ActivatedRoute
) { }
ngOnInit(): void {
this.tabView = this.page.getViewById('tab');
this.router.navigate([
{
outlets: {
homeTab: ['home'],
organizadorTab: ['organizador'],
favoritosTab: ['favoritos'],
categoriasTab: ['categorias'],
perfilTab: ['perfil']
}
}
], { relativeTo: this.route });
}
#HostListener('loaded')
onLoaded() {
if (platform.isAndroid) {
this.tabView.android.tabLayout.setTabTextFontSize(10);
const viewGroup = this.tabView.android.tabLayout.getChildAt(0);
for (let i = 0; i < viewGroup.getChildCount(); i++) {
const view = viewGroup.getChildAt(i);
const layoutParams = view.getLayoutParams();
layoutParams.width = 1;
layoutParams.leftMargin = -20;
layoutParams.rightMargin = -20;
layoutParams.topMargin = -20;
layoutParams.bottomMargin = -20;
view.setLayoutParams(layoutParams);
}
this.tabView.requestLayout();
}
}
}

common ActionBar and Side Drawer component for all pages native script

I have angular2-nativescript application with several pages. structure is similar to groceries example. All pages has very similar action bar content so I don't want to add all action bar and SideDrawer event handlers for each page or add custom component to each page template
Is there any way to have single ActionBar and SideDrawer component for all application pages? Also it is important to have the ability to access this component from all pages and call its methods from page class (so I can tell this component that it should hide/show some content). I want to use some action bar animation in future so my ActionBar shouldn't be recreated each time page changes
I created components that contain side drawer.
import { Component, ViewChild, OnInit,ElementRef,Input } from '#angular/core';
import { TranslateService } from "ng2-translate";
import {Router, NavigationExtras} from "#angular/router";
import * as ApplicationSettings from "application-settings";
import { Page } from "ui/page";
import { RadSideDrawerComponent, SideDrawerType } from "nativescript-pro-ui/sidedrawer/angular";
import application = require("application");
import { Config } from "../../shared/config";
import * as elementRegistryModule from 'nativescript-angular/element-registry';
import { RouterExtensions } from "nativescript-angular/router";
import { AndroidApplication, AndroidActivityBackPressedEventData } from "application";
import { isAndroid } from "platform";
import { UserService } from "../../shared/user/user.service";
import { SideDrawerLocation } from "nativescript-pro-ui/sidedrawer";
#Component({
selector: 'sideDrawer',
templateUrl: 'shared/sideDrawer/sideDrawer.component.html',
styleUrls: ['shared/sideDrawer/sideDrawer.component.css']
})
export class SideDrawerComponent implements OnInit {
#Input () title =""
#Input () backStatus =true;
theme: string="shared/sideDrawer/sideDrawer.component.ar.css";
private drawer: SideDrawerType;
private isEnglish=true;
#ViewChild(RadSideDrawerComponent)
public drawerComponent: RadSideDrawerComponent;
constructor(private us: UserService,private translate: TranslateService,private router:Router, private routerExtensions: RouterExtensions,private _page: Page) { }
ngOnInit() {
this.drawer = this.drawerComponent.sideDrawer;
this.drawer.showOverNavigation=true;
if (ApplicationSettings.getString("language")=="ar") {
this.isEnglish=false;
this.drawer.drawerLocation = SideDrawerLocation.Right;
this.addArabicStyleUrl();
}
if (!isAndroid) {
return;
}
application.android.on(AndroidApplication.activityBackPressedEvent, (data: AndroidActivityBackPressedEventData) => {
data.cancel = true; // prevents default back button behavior
this.back() ;
});
}
back() {
this.routerExtensions.back();
}
public toggleShow(){
this.drawer.showDrawer();
}
add it in every page and customize it using input and output parameters
<sideDrawer [title]="'category' | translate"></sideDrawer>

NativeScript one-way databinding doesn't update view

This seems like a pretty simple case to me, but I'm obviously missing something. I have a Model to be bound to the View. I then load the Model with an Http call. Why doesn't the View update? I thought that was the whole point of one-way binding.
I have verified that I'm getting back the data I'm expecting from the http call.
Update
I added a button to the screen and databinding will actually update the screen with the http loaded data for both fields on button push, even though the button method only sets one of the values. So either there's a bug in NativeScript or I'm not doing something incorrectly.
Update 2 Just the act of clicking the button will trigger the binding to happen. I've modified the code to have an empty tap handler, and just clicking the button makes it bind.
typescript
import { Component, ChangeDetectionStrategy, OnInit } from "#angular/core";
import { Job } from "../../shared/customer/job";
import { Http, Headers, Response } from "#angular/http";
import { Observable } from "rxjs/Rx";
#Component({
selector: "my-app",
templateUrl: "pages/job-details/job-details.html",
changeDetection: ChangeDetectionStrategy.OnPush
})
export class JobDetailsComponent implements OnInit {
job: Job;
salesAssociateName: string = "x";
constructor(private http: Http) {
this.job = new Job();
}
ngOnInit() {
this.getJob(1234);
}
getJob(leadId: number) {
var url = "https://url-not-for-you/job?franchiseeid=48&leadid=" + leadId;
var headers = this.createRequestHeader();
this.http.get(url, { headers: headers }).map(response => response.json())
.do(data => this.setData(data[0]))
.subscribe(
() => this.success(),
(error) => this.error()
);
}
private createRequestHeader() {
let headers = new Headers();
headers.append("AuthKey","blah");
headers.append("AuthToken", "blee");
return headers;
}
setData(job) {
this.job.FullName = job["FullName"];
this.job.SalesAssociateName = job["SalesAssociateName"];
this.salesAssociateName = this.job.SalesAssociateName;
console.log("Found job for customer: " + job["FullName"]);
}
success() {
// nothing useful
}
error() {
alert("There was a problem retrieving your customer job.");
}
changeSA() {
}
}
html
<StackLayout>
<Label [text]="job.FullName"></Label>
<Label [text]="salesAssociateName"></Label>
<Button text="Push" (tap)="changeSA()"></Button>
</StackLayout>
Your code will work as expected with the default ChangeDetectionStrategy. however, you have changed the strategy to onPush
In order to make your binding work as expected in the default changeStrategy delete the following line
changeDetection: ChangeDetectionStrategy.OnPush
or change it to
changeDetection: ChangeDetectionStrategy.Default
More about the Angular-2 ChangeDetectionStrategy here and here
If you still want to use onPush instead of the default strategy then your properties should be declared as #input() and once the change is made (in your case in setData) marked with markForCheck()
The reason your binding is working when triggered from Button tap is because
application state change can be triggered by:
Events - tap, swipe,
XHR - Fetching data from a remote server
Timers - e.g. setTimeout()
For testing purposes and if someone is interested of how to implement the scenario with onPush here is a sample code:
import { Component, ChangeDetectionStrategy, ChangeDetectorRef, OnInit, NgZone, Input } from "#angular/core";
import { Http, Headers, Response } from "#angular/http";
import { Observable as RxObservable } from "rxjs/Rx";
import "rxjs/add/operator/map";
import "rxjs/add/operator/do";
#Component({
selector: "my-app",
templateUrl: "app.component.html",
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AppComponent implements OnInit {
#Input() public job: any = { salesAssociateName: "default job" };
#Input() public salesAssociateName: string = "default name";
constructor(private http: Http, private change:ChangeDetectorRef) { }
ngOnInit() {
this.getJob();
}
getJob() {
var url = "http://httpbin.org/get";
var headers = this.createRequestHeader();
this.http.get(url, { headers: headers })
.map(response => response.json())
.do(data => {
this.setData();
}).subscribe(
() => this.success(),
(error) => this.error()
);
}
private createRequestHeader() {
let headers = new Headers();
return headers;
}
setData() {
this.job.salesAssociateName = "NEW job SalesAssociateName";
this.salesAssociateName = "NEW job FullName";
this.change.markForCheck();
}
success() {
alert("success");
}
error() {
alert("There was a problem retrieving your customer job.");
}
}

NativeScript handling back button event

I am trying to handle the hardware back button in a NativeScript app. I am using NativeScript version 2.3.0 with Angular.
Here is what I have in main.ts file
// this import should be first in order to load some required settings (like globals and reflect-metadata)
import { platformNativeScriptDynamic, NativeScriptModule } from "nativescript-angular/platform";
import { NgModule,Component,enableProdMode } from "#angular/core";
import { AppComponent } from "./app.component";
import { NativeScriptRouterModule } from "nativescript-angular/router";
import { routes, navigatableComponents } from "./app.routing";
import { secondComponent } from "./second.component";
import {AndroidApplication} from "application";
#Component({
selector: 'page-navigation-test',
template: `<page-router-outlet></page-router-outlet>`
})
export class PageNavigationApp {
}
#NgModule({
declarations: [AppComponent,PageNavigationApp,secondComponent
// ...navigatableComponents
],
bootstrap: [PageNavigationApp],
providers:[AndroidApplication],
imports: [NativeScriptModule,
NativeScriptRouterModule,
NativeScriptRouterModule.forRoot(routes)
],
})
class AppComponentModule {
constructor(private androidapplication:AndroidApplication){
this.androidapplication.on("activityBackPressed",()=>{
console.log("back pressed");
})
}
}
enableProdMode();
platformNativeScriptDynamic().bootstrapModule(AppComponentModule);
I am importing application with
import {AndroidApplication} from "application";
Then in the constrouctor of appComponentModule I am registering the event for activityBackPressed and just doing a console.log.
This does not work.
What am I missing here?
I'm using NativeScript with Angular as well and this seems to work quite nicely for me:
import { RouterExtensions } from "nativescript-angular";
import * as application from "tns-core-modules/application";
import { AndroidApplication, AndroidActivityBackPressedEventData } from "tns-core-modules/application";
export class HomeComponent implements OnInit {
constructor(private router: Router) {}
ngOnInit() {
if (application.android) {
application.android.on(AndroidApplication.activityBackPressedEvent, (data: AndroidActivityBackPressedEventData) => {
if (this.router.isActive("/articles", false)) {
data.cancel = true; // prevents default back button behavior
this.logout();
}
});
}
}
}
Note that hooking into the backPressedEvent is a global thingy so you'll need to check the page you're on and act accordingly, per the example above.
import { Component, OnInit } from "#angular/core";
import * as Toast from 'nativescript-toast';
import { Router } from "#angular/router";
import * as application from 'application';
#Component({
moduleId: module.id,
selector: 'app-main',
templateUrl: './main.component.html',
styleUrls: ['./main.component.css']
})
export class MainComponent {
tries: number = 0;
constructor(
private router: Router
) {
if (application.android) {
application.android.on(application.AndroidApplication.activityBackPressedEvent, (args: any) => {
if (this.router.url == '/main') {
args.cancel = (this.tries++ > 0) ? false : true;
if (args.cancel) Toast.makeText("Press again to exit", "long").show();
setTimeout(() => {
this.tries = 0;
}, 2000);
}
});
}
}
}
Normally you should have an android activity and declare the backpress function on that activity. Using AndroidApplication only is not enough. Try this code:
import {topmost} from "ui/frame";
import {AndroidApplication} from "application";
let activity = AndroidApplication.startActivity ||
AndroidApplication.foregroundActivity ||
topmost().android.currentActivity ||
topmost().android.activity;
activity.onBackPressed = function() {
// Your implementation
}
You can also take a look at this snippet for example
As far as I know, NativeScript has a built-in support for this but it's not documented at all.
Using onBackPressed callback, you can handle back button behaviour for View components (e.g. Frame, Page, BottomNavigation).
Example:
function pageLoaded(args) {
var page = args.object;
page.onBackPressed = function () {
console.log("Returning true will block back button default behaviour.");
return true;
};
page.bindingContext = homeViewModel;
}
exports.pageLoaded = pageLoaded;
What's tricky here is to find out which view handles back button press in your app. In my case, I used a TabView that contained pages but the TabView itself handled the event instead of current page.

Resources