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

How to setup ‘nativescript-stripe’ in NativeScript Vue

我正在尝试在我的 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) 相同的代码,只是我设置了 publishableKey 和 backendBaseURL,并导出了一个 class 对于 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 class 也移到它自己的文件中,但不要相信这就是现在的问题。

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

CONSOLE ERROR file:///node_modules/nativescript-vue/dist/index.js:2129:21 [Vue warn]: Error in mounted hook: "TypeError: _shared_stripe_stripe_service__WEBPACK_IMPORTED_MODULE_6__["StripeService"].createPaymentSession is not a function. (In '_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__["StripeService"].createPaymentSession' is undefined)"

编辑:

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

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 width="32" height="20" :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();
  }
}

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

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

有人吗?

我花了好几个小时终于弄明白了。感谢@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 width="32" height="20" :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