如何在 NativeScript Vue 中设置“nativescript-stripe”

Posted

技术标签:

【中文标题】如何在 NativeScript Vue 中设置“nativescript-stripe”【英文标题】:How to setup ‘nativescript-stripe’ in NativeScript Vue 【发布时间】:2020-01-04 05:03:44 【问题描述】:

我正在尝试在我的 Nativescript Vue 应用程序中设置 «nativescript-stripe» 插件。跟随插件 github 上的演示有点困难,因为他们只有 Angular and TypeScript 的演示。有没有人让StandardComponent 与 Vue 一起工作,并且可以告诉我在哪里以及将哪些参数发送到StripeService.createPaymentSession()

我尝试在模板中设置<Page ref=«cartPage»> 并在mounted() 设置paymentSession:

import 
  StripeAddress,
  StripePaymentData,
  StripePaymentListener,
  StripePaymentMethod,
  StripePaymentSession,
  StripeShippingMethod,
  StripeShippingMethods
 from "nativescript-stripe/standard";
import  StripeService, Listener  from "~/shared/stripe/stripe.service";

var paymentSession = ;

export default 
  mounted() 
    //not sure if this is the way to do it
    paymentSession = StripeService.createPaymentSession(
      this.$refs.cartPage,
      this.stripeItem.price,
      new Listener(this)
    );
  ,

在我的 stripe.service.ts 文件中,我得到了与 Angular 演示 (https://github.com/triniwiz/nativescript-stripe/blob/master/demo-angular/app/demo/stripe.service.ts) 相同的代码,除了我设置了可发布密钥和 backendBaseURL,并且还为 Listener 导出了一个类:

export class Listener 
  public component;
  constructor(component) 
      this.component = component;
  
  onCommunicatingStateChanged(_isCommunicating) 
      this.component.changeDetectionRef.detectChanges();
  
//etc. (code from https://github.com/triniwiz/nativescript-stripe/blob/master/demo-angular/app/demo/standard.component.ts)

我想也许我也应该将 Listener 类移到它自己的文件中,但不要相信这是现在的问题。

应用程序崩溃并显示错误消息:

控制台错误文件:///node_modules/nativescript-vue/dist/index.js:2129:21 [Vue 警告]:挂载钩子错误: "TypeError: _shared_stripe_stripe_service__WEBPACK_IMPORTED_MODULE_6__["StripeService"].createPaymentSession 不是函数。 (在 '_shared_stripe_stripe_service__WEBPACK_IMPORTED_MODULE_6__["StripeService"].createPaymentSession(this.$refs.cartPage, this.stripeItem.price, new _shared_stripe_stripe_service__WEBPACK_IMPORTED_MODULE_6__"Listener")', '_shared_stripe_stripe_service__WEBPACK_IMPORTED_MODULE_6__[" >

编辑:

我终于能够使用此设置运行应用程序:

ShoppingCart.vue:

<template>
  <Page ref="cartPage" class="page">
    <ActionBar class="action-bar">
      <NavigationButton ios:visibility="collapsed" icon="res://menu" @tap="onDrawerButtonTap"></NavigationButton>
      <ActionItem
        icon="res://navigation/menu"
        android:visibility="collapsed"
        @tap="onDrawerButtonTap"
        ios.position="left"
      ></ActionItem>
      <Label class="action-bar-title" text="ShoppingCart"></Label>
    </ActionBar>
    <StackLayout class="page p-10">
              <GridLayout rows="auto" columns="auto,*">
                <Label row="0" col="0" :text="stripeItem.name" class="h2"></Label>
                <Label
                  row="0"
                  col="1"
                  :text="'kr' + stripeItem.price"
                  class="text-right text-muted"
                ></Label>
              </GridLayout>
              <StackLayout class="hr-light m-10"></StackLayout>
              <GridLayout
                rows="auto"
                columns="*,auto"
                @tap="showPaymentMethods()"
                class="list-group-item"
              >
                <Label row="0" col="0" text="Payment Type"></Label>
                <StackLayout row="0" col="1" orientation="horizontal">
                  <Image   :src="paymentImage"></Image>
                  <Label
                    :text="paymentType"
                    class="text-right text-muted"
                    :visibility="!isLoading ? 'visible' : 'collapse'"
                  ></Label>
                </StackLayout>
                <ActivityIndicator
                  row="0"
                  col="1"
                  :busy="isLoading"
                  :visibility="isLoading ? 'visible' : 'collapse'"
                ></ActivityIndicator>
              </GridLayout>
              <StackLayout class="hr-light m-10"></StackLayout>
              <GridLayout rows="auto" columns="auto,*" @tap="showShipping()" class="list-group-item">
                <Label row="0" col="0" text="Shipping Method"></Label>
                <Label row="0" col="1" :text="shippingType" class="text-right text-muted"></Label>
              </GridLayout>
              <StackLayout class="hr-light m-10"></StackLayout>
              <GridLayout rows="auto" columns="auto,*" class="list-group-item">
                <Label row="0" col="0" text="Total"></Label>
                <Label row="0" col="1" :text="'kr ' + total" class="text-right"></Label>
              </GridLayout>
              <StackLayout class="hr-light m-10"></StackLayout>
              <Label :text="errorMessage" class="text-danger" textWrap="true"></Label>
              <Button text="Buy" :isEnabled="canBuy" class="btn btn-primary btn-active" @tap="buy()"></Button>
              <ActivityIndicator
                :busy="paymentInProgress"
                :visibility="paymentInProgress ? 'visible' : 'collapse'"
              ></ActivityIndicator>
              <Label :text="successMessage" class="text-primary" textWrap="true"></Label>
              <StackLayout class="hr-light m-10"></StackLayout>
              <Label text="Debug Info"></Label>
              <Label :text="debugInfo" class="body" textWrap="true"></Label>
            </StackLayout>
  </Page>
</template>

<script>
import * as utils from "~/shared/utils";
import SelectedPageService from "../shared/selected-page-service";
import  StripeService, Listener  from "~/shared/stripe/stripe.service.ts";

const Page = require("tns-core-modules/ui/page").Page;

let stripeService = new StripeService();
var paymentSession = ;

export default 
  mounted() 
    SelectedPageService.getInstance().updateSelectedPage("ShoppingCart");

    paymentSession = stripeService.createPaymentSession(new Page(), 1213, new Listener(this));
  ,
  data() 
    return 
      stripeItem: 
        id: 0,
        name: "Something to buy",
        price: 1200
      ,
      paymentInProgress: false,
      canBuy: true,
      isLoading: false,
      paymentType: "",
      paymentImage: "",
      shippingType: "",
      total: "",
      debugInfo: "",
      successMessage: "",
      errorMessage: ""
    ;
  ,
  methods: 
    onDrawerButtonTap() 
      utils.showDrawer();
    ,
    showPaymentMethods() 
      return stripeService.showPaymentMethods(paymentSession);
    ,
    showShipping() 
      return stripeService.showShipping(paymentSession);
    ,
    buy() 
      this.paymentInProgress = true;
      this.canBuy = false;
      return stripeService.requestPayment(paymentSession);
    
  
;
</script>

stripe.service.ts

import  StripeAddress, StripeBackendAPI, StripeConfig, StripeCustomerSession, StripePaymentListener, StripePaymentSession, StripeShippingAddressField, StripeShippingMethod  from "nativescript-stripe/standard";
import * as httpModule from "tns-core-modules/http";
import  Page  from "tns-core-modules/ui/page";

export const publishableKey = "pk_test_xxxxremovedxxxx";
const backendBaseURL = "https://xxxxremovedxxxx.herokuapp.com/";
const appleMerchantID = "";

export class Listener 

  public component;
  constructor(component) 
      this.component = component;
  
  onCommunicatingStateChanged(_isCommunicating) 

  
  onPaymentDataChanged(data) 
      this.component.paymentMethod = data.paymentMethod;
      this.component.shippingInfo = data.shippingInfo;
      this.component.shippingAddress = data.shippingAddress;
  
  onPaymentSuccess() 
      this.component.successMessage =
          `Congratulations! You bought a "$this.component.item.name" for $$this.component.item.price / 100.`;

  
  onUserCancelled() 
  
  onError(_errorCode, message) 
      this.component.errorMessage = message;
  
  provideShippingMethods(address) 
      let upsGround = 
          amount: 0,
          label: "UPS Ground",
          detail: "Arrives in 3-5 days",
          identifier: "ups_ground"
      ;
      let upsWorldwide = 
          amount: 1099,
          label: "UPS Worldwide Express",
          detail: "Arrives in 1-3 days",
          identifier: "ups_worldwide"
      ;
      let fedEx = 
          amount: 599,
          label: "FedEx",
          detail: "Arrives tomorrow",
          identifier: "fedex"
      ;
      let methods = ;
      if (!address.country || address.country === "US") 
          methods['isValid'] = true;
          methods['validationError'] = undefined;
          methods['shippingMethods'] = [upsGround, fedEx];
          methods['selectedShippingMethod'] = fedEx;
      
      else if (address.country === "AQ") 
          methods['isValid'] = false;
          methods['validationError'] = "We can't ship to this country.";
      
      else 
          fedEx.amount = 2099;
          methods['isValid'] = true;
          methods['validationError'] = undefined;
          methods['shippingMethods'] = [upsWorldwide, fedEx];
          methods['selectedShippingMethod'] = fedEx;
      
      return methods;
  



export class StripeService implements StripeBackendAPI 
  private customerSession: StripeCustomerSession;

  constructor() 
    if (-1 !== publishableKey.indexOf("pk_test_yours")) 
      throw new Error("publishableKey must be changed from placeholder");
    
    if (-1 !== backendBaseURL.indexOf("https://yours.herokuapp.com/")) 
      throw new Error("backendBaseURL must be changed from placeholder");
    

    StripeConfig.shared().backendAPI = this;
    StripeConfig.shared().publishableKey = publishableKey;
    StripeConfig.shared().appleMerchantID = appleMerchantID;
    StripeConfig.shared().companyName = "Demo Company";
    StripeConfig.shared().requiredShippingAddressFields = [StripeShippingAddressField.PostalAddress];

    this.customerSession = new StripeCustomerSession();
  

  private backendURL(pathComponent: string): string 
    if (!backendBaseURL) throw new Error("backendBaseURL must be set");
    if (!backendBaseURL.endsWith("/")) 
      return backendBaseURL + "/" + pathComponent;
     else 
      return backendBaseURL + pathComponent;
    
  

  createCustomerKey(apiVersion: string): Promise<any> 
    let url = this.backendURL("ephemeral_keys");
    return httpModule.request(
      url: url,
      method: "POST",
      headers:  "Content-Type": "application/x-www-form-urlencoded; charset=utf-8" ,
      content: "api_version=" + apiVersion
    ).then(response => 
      if (response.statusCode < 200 || response.statusCode >= 300) 
        throw new Error(response.content.toString());
      
      return response.content.toJSON();
    );
  

  completeCharge(stripeID: string, amount: number, shippingMethod: StripeShippingMethod, shippingAddress: StripeAddress): Promise<void> 
    let url = this.backendURL("capture_payment");
    return httpModule.request(
      url: url,
      method: "POST",
      headers:  "Content-Type": "application/x-www-form-urlencoded; charset=utf-8" ,
      content:
        "source=" + stripeID +
        "&amount=" + amount +
        "&" + this.encodeShipping(shippingMethod, shippingAddress)
    ).then(response => 
      if (response.statusCode < 200 || response.statusCode >= 300) 
        throw new Error(response.content.toString());
      
    );
  

  private encodeShipping(method: StripeShippingMethod, address: StripeAddress): string 
    function entry(label: string, value: string): string 
      return value ? encodeURI(label) + "=" + encodeURI(value) : "";
    
    return entry("shipping[carrier]", method.label) +
      entry("&shipping[name]", address.name) +
      entry("&shipping[address][line1]", address.line1) +
      entry("&shipping[address][line2]", address.line2) +
      entry("&shipping[address][city]", address.city) +
      entry("&shipping[address][state]", address.state) +
      entry("&shipping[address][country]", address.country) +
      entry("&shipping[address][postal_code]", address.postalCode) +
      entry("&phone", address.phone) +
      entry("&email", address.email);
  

  createPaymentSession(page, price, listener?): StripePaymentSession 
    return new StripePaymentSession(page, this.customerSession, price, "usd", listener);
  

  showPaymentMethods(paymentSession: StripePaymentSession) 
    paymentSession.presentPaymentMethods();
  

  showShipping(paymentSession: StripePaymentSession) 
    paymentSession.presentShipping();
  

  requestPayment(paymentSession: StripePaymentSession) 
    paymentSession.requestPayment();
  

我现在面临的问题(也是设置的一部分)是当我点击“付款类型”时没有任何反应。当我调试时,我可以看到它进入了方法 presentPaymentMethods()。插件中的这段代码运行没有任何错误,但没有任何反应:

 StripePaymentSession.prototype.presentPaymentMethods = function () 
        this.ensureHostViewController();
        this.native.presentPaymentOptionsViewController();
    ;

有人吗?

【问题讨论】:

你不能简单的构造new Page(),它应该是你的组件所在的页面。如果您仍然有,请分享完整的干净代码,包括您在服务中的所有内容并发布错误日志。 我已经用完整的代码@Manoj 更新了帖子 我尝试了“require('ui/frame').topmost().currentPage”而不是“new Page()”。仍然没有错误,视图没有变化@Manoj 使用cartPageloaded事件并将其作为args.object传递,其中args应该是加载事件回调的参数。 我照你说的做了,args.object 有一个道具typeName="Page" 所以我怀疑它是正确的对象,但仍然没有。我的页面加载事件现在看起来像这样:onPageLoaded(args) var comp = this; paymentSession = stripeService.createPaymentSession( args.object, comp.stripeItem.price, new Listener(comp) ); 【参考方案1】:

花了好几个小时终于弄明白了。感谢@Manoj 的提示。

stripe.service.ts:

import  StripeAddress, StripeBackendAPI, StripeConfig, StripeCustomerSession, StripePaymentListener, StripePaymentSession, StripeShippingAddressField, StripeShippingMethod  from "nativescript-stripe/standard";
import * as httpModule from "tns-core-modules/http";

// 1) To get started with this demo, first head to https://dashboard.stripe.com/account/apikeys
// and copy your "Test Publishable Key" (it looks like pk_test_abcdef) into the line below.
export const publishableKey = "pk_test_yours";

// 2) Next, optionally, to have this demo save your user's payment details, head to
// https://github.com/stripe/example-ios-backend , click "Deploy to Heroku", and follow
// the instructions (don't worry, it's free). Paste your Heroku URL below
// (it looks like https://blazing-sunrise-1234.herokuapp.com ).
const backendBaseURL = "https://yours.herokuapp.com/";

// 3) Optionally, to enable Apple Pay, follow the instructions at https://stripe.com/docs/apple-pay/apps
// to create an Apple Merchant ID. Paste it below (it looks like merchant.com.yourappname).
const appleMerchantID = "";

export class Listener 

  public component;
  constructor(component) 
    this.component = component;
  
  onCommunicatingStateChanged(_isCommunicating) 

  
  onPaymentDataChanged(data) 
    this.component.paymentMethod = data.paymentMethod;
    this.component.shippingInfo = data.shippingInfo;
    this.component.shippingAddress = data.shippingAddress;
  
  onPaymentSuccess() 
    this.component.successMessage =
      `Congratulations! You bought a "$this.component.stripeItem.name" for $$this.component.stripeItem.price / 100.`;

  
  onUserCancelled() 
  
  onError(_errorCode, message) 
    this.component.errorMessage = message;
  
  provideShippingMethods(address) 
    let upsGround = 
      amount: 0,
      label: "UPS Ground",
      detail: "Arrives in 3-5 days",
      identifier: "ups_ground"
    ;
    let upsWorldwide = 
      amount: 1099,
      label: "UPS Worldwide Express",
      detail: "Arrives in 1-3 days",
      identifier: "ups_worldwide"
    ;
    let fedEx = 
      amount: 599,
      label: "FedEx",
      detail: "Arrives tomorrow",
      identifier: "fedex"
    ;
    let methods = ;
    if (!address.country || address.country === "US") 
      methods['isValid'] = true;
      methods['validationError'] = undefined;
      methods['shippingMethods'] = [upsGround, fedEx];
      methods['selectedShippingMethod'] = fedEx;
    
    else if (address.country === "AQ") 
      methods['isValid'] = false;
      methods['validationError'] = "We can't ship to this country.";
    
    else 
      fedEx.amount = 2099;
      methods['isValid'] = true;
      methods['validationError'] = undefined;
      methods['shippingMethods'] = [upsWorldwide, fedEx];
      methods['selectedShippingMethod'] = fedEx;
    
    return methods;
  



export class StripeService implements StripeBackendAPI 
  private customerSession;

  constructor() 
    if (-1 !== publishableKey.indexOf("pk_test_yours")) 
      throw new Error("publishableKey must be changed from placeholder");
    
    if (-1 !== backendBaseURL.indexOf("https://yours.herokuapp.com/")) 
      throw new Error("backendBaseURL must be changed from placeholder");
    

    StripeConfig.shared().backendAPI = this;
    StripeConfig.shared().publishableKey = publishableKey;
    StripeConfig.shared().appleMerchantID = appleMerchantID;
    StripeConfig.shared().companyName = "Demo Company";
    StripeConfig.shared().requiredShippingAddressFields = [StripeShippingAddressField.PostalAddress];

    this.customerSession = new StripeCustomerSession();
  

  private backendURL(pathComponent: string): string 
    if (!backendBaseURL) throw new Error("backendBaseURL must be set");
    if (!backendBaseURL.endsWith("/")) 
      return backendBaseURL + "/" + pathComponent;
     else 
      return backendBaseURL + pathComponent;
    
  

  createCustomerKey(apiVersion: string): Promise<any> 
    let url = this.backendURL("ephemeral_keys");
    return httpModule.request(
      url: url,
      method: "POST",
      headers:  "Content-Type": "application/x-www-form-urlencoded; charset=utf-8" ,
      content: "api_version=" + apiVersion
    ).then(response => 
      if (response.statusCode < 200 || response.statusCode >= 300) 
        throw new Error(response.content.toString());
      
      return response.content.toJSON();
    );
  

  completeCharge(stripeID: string, amount: number, shippingMethod: StripeShippingMethod, shippingAddress: StripeAddress): Promise<void> 
    let url = this.backendURL("capture_payment");
    return httpModule.request(
      url: url,
      method: "POST",
      headers:  "Content-Type": "application/x-www-form-urlencoded; charset=utf-8" ,
      content:
        "source=" + stripeID +
        "&amount=" + amount +
        "&" + this.encodeShipping(shippingMethod, shippingAddress)
    ).then(response => 
      if (response.statusCode < 200 || response.statusCode >= 300) 
        throw new Error(response.content.toString());
      
    );
  

  private encodeShipping(method: StripeShippingMethod, address: StripeAddress): string 
    function entry(label: string, value: string): string 
      return value ? encodeURI(label) + "=" + encodeURI(value) : "";
    
    return entry("shipping[carrier]", method.label) +
      entry("&shipping[name]", address.name) +
      entry("&shipping[address][line1]", address.line1) +
      entry("&shipping[address][line2]", address.line2) +
      entry("&shipping[address][city]", address.city) +
      entry("&shipping[address][state]", address.state) +
      entry("&shipping[address][country]", address.country) +
      entry("&shipping[address][postal_code]", address.postalCode) +
      entry("&phone", address.phone) +
      entry("&email", address.email);
  

  createPaymentSession(page, price, listener?): StripePaymentSession 
    return new StripePaymentSession(page, this.customerSession, price, "usd", listener);
  

  showPaymentMethods(paymentSession: StripePaymentSession) 
    paymentSession.presentPaymentMethods();
  

  showShipping(paymentSession: StripePaymentSession) 
    paymentSession.presentShipping();
  

  requestPayment(paymentSession: StripePaymentSession) 
    paymentSession.requestPayment();
  

Payment.vue:

<template>
  <Page @loaded="onPageLoaded" class="page">
    <ActionBar class="action-bar">
      <Label class="action-bar-title" text="Home"></Label>
    </ActionBar>

    <StackLayout class="page p-10">
      <GridLayout rows="auto" columns="auto,*">
        <Label row="0" col="0" :text="stripeItem.name" class="h2"></Label>
        <Label row="0" col="1" :text="'$' + stripeItem.price" class="text-right text-muted"></Label>
      </GridLayout>
      <StackLayout class="hr-light m-10"></StackLayout>
      <GridLayout rows="auto" columns="*,auto" @tap="showPaymentMethods()" class="list-group-item">
        <Label row="0" col="0" text="Payment Type"></Label>
        <StackLayout row="0" col="1" orientation="horizontal">
          <Image   :src="paymentImage"></Image>
          <Label
            :text="paymentType"
            class="text-right text-muted"
            :visibility="!isLoading ? 'visible' : 'collapse'"
          ></Label>
        </StackLayout>
        <ActivityIndicator
          row="0"
          col="1"
          :busy="isLoading"
          :visibility="isLoading ? 'visible' : 'collapse'"
        ></ActivityIndicator>
      </GridLayout>
      <StackLayout class="hr-light m-10"></StackLayout>
      <GridLayout rows="auto" columns="auto,*" @tap="showShipping()" class="list-group-item">
        <Label row="0" col="0" text="Shipping Method"></Label>
        <Label row="0" col="1" :text="shippingType" class="text-right text-muted"></Label>
      </GridLayout>
      <StackLayout class="hr-light m-10"></StackLayout>
      <GridLayout rows="auto" columns="auto,*" class="list-group-item">
        <Label row="0" col="0" text="Total"></Label>
        <Label row="0" col="1" :text="'$ ' + total" class="text-right"></Label>
      </GridLayout>
      <StackLayout class="hr-light m-10"></StackLayout>
      <Label :text="errorMessage" class="text-danger" textWrap="true"></Label>
      <Button text="Buy" :isEnabled="canBuy" class="btn btn-primary btn-active" @tap="buy()"></Button>
      <ActivityIndicator
        :busy="paymentInProgress"
        :visibility="paymentInProgress ? 'visible' : 'collapse'"
      ></ActivityIndicator>
      <Label :text="successMessage" class="text-primary" textWrap="true"></Label>
      <StackLayout class="hr-light m-10"></StackLayout>
      <Label text="Debug Info"></Label>
      <Label :text="debugInfo" class="body" textWrap="true"></Label>
    </StackLayout>
  </Page>
</template>

<script>
//Change the import of 'stripe.service.ts' to the right path
import  StripeService, Listener  from "~/services/stripe.service.ts";
let stripeService = new StripeService();
var paymentSession = null;
export default 
  data() 
    return 
      stripeItem: 
        id: 0,
        name: "Something to buy",
        price: 1200
      ,
      paymentInProgress: false,
      canBuy: true,
      isLoading: false,
      paymentType: "",
      paymentImage: "",
      shippingType: "",
      total: "",
      debugInfo: "",
      successMessage: "",
      errorMessage: ""
    ;
  ,
  methods: 
    onPageLoaded(args) 
      var comp = this;
      paymentSession = stripeService.createPaymentSession(
        args.object,
        comp.stripeItem.price,
        new Listener(comp)
      );
    ,
    showPaymentMethods() 
      return stripeService.showPaymentMethods(paymentSession);
    ,
    showShipping() 
      return stripeService.showShipping(paymentSession);
    ,
    buy() 
      this.paymentInProgress = true;
      this.canBuy = false;
      return stripeService.requestPayment(paymentSession);
    
  
;
</script>

并确保您已安装 TypeScript tns install typescript

【讨论】:

以上是关于如何在 NativeScript Vue 中设置“nativescript-stripe”的主要内容,如果未能解决你的问题,请参考以下文章

如何在 nativescript 中设置方向

如何在 Nativescript HtmlView 标签中设置链接颜色?

NativeScript-Vue 中的分组 UITableView

NativeScript - ios如何在键盘出现时调整视图

如何在我的 Vue 项目中设置 vue-burger-menu

Vue.js:如何在 .vue 模板文件中的 vue 方法中设置数据变量