Meteor 1.3 + React Stripe订阅

时间:2016-05-04 12:35:29

标签: meteor reactjs

我跟随TheMeteorChef使用Meteor构建SaaS:使用blaze模板构建的Stripe。尝试使用反应,但我认为我在路上的某个地方失败了。我已经得到了2的第1部分中的大约一半,但足以测试注册计划是否有效。嗯,它不起作用,但也没有在控制台中给出任何错误......我的经验很少,实际上刚开始,所以我希望能得到一些帮助。谢谢。

〜/客户/助手/ stripe.js

Meteor.startup(function() {
  const stripeKey = Meteor.settings.public.stripe.testPublishableKey;
  Stripe.setPublishableKey(stripeKey);

  STRIPE = {
    getToken: function(domElement, card, callback) {
      Stripe.card.createToken(card, function(status, response) {
        if(response.error) {
          Bert.alert(response.error.message, "danger");
        } else {
          STRIPE.setToken(response.id, domElement, callback);
        }
      });
    },
    setToken: function(token, domElement, callback) {
      $(domElement).append($('<input type="hidden" name="stripeToken" />').val(token));
      callback();
    }
  }
});

〜/客户端/组件/ SignUp.jsx

import React, {Component} from 'react';

import PlanSelectForm from '../components/PlanSelectForm.jsx';
import CreditCardForm from '../components/CreditCardForm.jsx';

export default class SignUp extends Component {
  componentDidMount() {
    $.validator.addMethod('usernameRegex', function(value, element) {
      return this.optional(element) || /^[a-zA-Z0-9-_]+$/i.test(value);
    }, "Username must contain only letters, numbers, underscores and dashes.");
    $('#application-signup').validate({
      rules: {
        username: {
          required: true,
          usernameRegex: true,
          minlength: 6
        },
        emailAddress: {
          required: true,
          email: true
        },
        password: {
          required: true,
          minlength: 6
        }
      },
      messages: {
        username: {
          required: 'You can\'t leave this empty',
          usernameRegex: 'You can use letter, numbers, underscores, and dashes.',
          minlength: 'Too short. Use at least 6 characters.'
        },
        emailAddress: {
          required: 'You can\'t leave this empty',
          email: 'Email is invalid or already taken.'
        },
        password: {
          required: 'You can\'t leave this empty',
          minlength: 'Too short. Use at least 6 characters.'
        }
      },
      handleSubmit: function() {
        STRIPE.getToken('#application-signup', {
          number: $('[data-stripe="cardNumber"]').val(),
          exp_month: $('[data-stripe="expMo"]').val(),
          exp_year: $('[data-stripe="expYr"]').val(),
          cvc: $('[data-stripe="cvc"]').val()
        }, function() {
          const customer = {
            username: $('[name="username"]').val(),
            emailAddress: $('[name="emailAddress"]').val(),
            password: $('[name="password"]').val(),
            plan: $('[name="selectPlan"]:checked').val(),
            token: $('[name="stripeToken"]').val()
          };

          const submitButton = $('input[type="submit"]').button('loading');

          Meteor.call('createTrialCustomer', customer, function(error, response) {
            if(error) {
              alert(error.reason);
              submitButton.button('reset');
            } else {
              if(response.error) {
                alert(response.message);
                submitButton.button('reset');
              } else {
                Meteor.loginWithPassword(customer.emailAddress, customer.password, function(error) {
                  if(error) {
                    alert(error.reason);
                    submitButton.button('reset');
                  } else {
                    Router.go('/chart');
                    submitButton.button('reset');
                  }
                });
              }
            }
          });
        });
      }
    });
  }

  render() {
    console.log(this);
    return (
      <form id="application-signup" className="signup">
        <h4>Account details</h4>
        <div className="form-group">
          <label for="username">Username</label>
          <input type="text"
                 name="username"
                 className="form-control"
                 placeholder="Username" />
        </div>
        <div className="form-group">
          <label for="emailAddress">Email Address</label>
          <input type="email"
                 name="emailAddress"
                 className="form-control"
                 placeholder="Email Address" />
        </div>
        <div className="form-group">
          <label for="password">Password</label>
          <input type="password"
                 name="password"
                 className="form-control"
                 placeholder="Password" />
        </div>
        <h4 className="page-header">Payment Information</h4>
        <label>Which plan sounds <em>amazing</em>?</label>
        <PlanSelectForm />
        <div className="form-group">
          <CreditCardForm />{/* data={signup} /> */}
        </div>
        <div className="form-group">
          <input type="submit"
                 className="btn btn-success btn-block"
                 data-loading-text="Setting up your trial..."
                 value="Put me on the rocketship" />
        </div>
      </form>
    )
  }
}

注意:在教程中,TheMeteorChef使用CreditCardForm的动态模板,数据=&#34;注册&#34;上下文。我认为他提到CC模板会在之后再次使用,但我还没有走得那么远。无论如何,我不知道&#34;注册&#34;意思是......所以我把它留下了评论。如果您知道,也请告诉我。

〜/客户端/组件/ PlanSelectForm.jsx

import React, {Component} from 'react';

export default class PlanSelectForm extends Component {
  componentDidMount() {
    const firstPlanItem = $('.select-plan a:first-child');
    firstPlanItem.addClass('active');
    firstPlanItem.find('input').prop('checked', true);
  }

  plans() {
    return Meteor.settings.public.plans;
  }

  handleClickItem(e) {
    const parent = $(e.target).closest('.list-group-item');
    console.log(parent);
    parent.addClass('active');
    $('.list-group-item').not(parent).removeClass('active');
    $('.list-group-item').not(parent).find('input[type="radio"]').prop('checked', false);
    parent.find('input[type="radio"]').prop('checked', true);
  }

  render() {
    let plans = this.plans();
    if(!plans) {
      return(<div>loading...</div>);
    }
    return (
      <div className="list-group select-plan">
        {plans.map((plan) => {
          return (
            <a key={plan.id}
               href="#"
               className="list-group-item"
               onClick={this.handleClickItem.bind(this)}>
              <input key={plan.id}
                     type="radio"
                     ref="selectPlan"
                     id={`selectPlan_${plan.id}`}
                     value={plan.name} />
              {plan.name} {plan.amount.usd}/{plan.interval}
            </a>
          )
        })}
      </div>
    )
  }
}

〜/客户端/组件/ CreditCardForm.jsx

import React, {Component} from 'react';

export default class CreditCardForm extends Component {
  render() {
    return (
      <div>
        <div className="row">
          <div className="col-xs-12">
            <div className="form-group">
              <label className="text-success">
                <i className="fa fa-lock"></i> Card Number
              </label>
              <input type="text"
                     data-stripe="cardNumber"
                     className="form-control card-number"
                     placeholder="Card Number" />
            </div>
          </div>
        </div>
        <div className="row">
          <div className="col-xs-4">
            <label>Exp. Mo.</label>
            <input type="text"
                   data-stripe="expMo"
                   className="form-control exp-month"
                   placeholder="Exp. Mo." />
          </div>
          <div className="col-xs-4">
            <label>Exp. Yr.</label>
            <input type="text"
                   data-stripe="expYr"
                   className="form-control exp-year"
                   placeholder="Exp. Yr." />
          </div>
          <div className="col-xs-4">
            <label>CVC</label>
            <input type="text"
                   data-stripe="cvc"
                   className="form-control cvc"
                   placeholder="CVC" />
          </div>
        </div>
      </div>
    )
  }
}

〜/服务器/ signup.js

Meteor.methods({
  createTrialCustomer: function(customer) {
    check(customer, {
      name: String,
      emailAddress: String,
      password: String,
      plan: String,
      token: String
    });

    const emailRegex = new RegExp(customer.emailAddress, 'i');
    const usernameRegex = new RegExp(customer.username, 'i');
    const lookupEmail = Meteor.users.findOne({'emails.address': emailRegex});
    const lookupUser = Meteor.users.findOne({'username': usernameRegex});

    if(!lookupEmail) {
      if(!lookupUser) {
        const newCustomer = new Future();

        Meteor.call('stripeCreateCustomer', customer.token, customer.emailAddress, function(error, stripeCustomer) {
          if(error) {
            console.log(error);
          } else {
            const customerId = stripeCustomer.id,
                  plan       = customer.plan;

            Meteor.call('stripeCreateSubscription', customerId, plan, function(error, response) {
              if(error) {
                console.log(error);
              } else {
                try {
                  const user = Accounts.createUser({
                    username: customer.username,
                    email: customer.emailAddress,
                    password: customer.password
                  });

                  const subscription = {
                    customerId: customerId,
                    subscription: {
                      plan: {
                        name: customer.plan,
                        used: 0
                      },
                      payment: {
                        card: {
                          type: stripeCustomer.sources.data[0].brand,
                          lastFour: stripeCustomer.sources.data[0].last4
                        },
                        nextPaymentDue: response.current_period_end
                      }
                    }
                  }
                  Meteor.users.update(user, {
                    $set: subscription
                  }, function(error, response) {
                    if(error) {
                      console.log(error);
                    } else {
                      newCustomer.return(user);
                    }
                  });
                } catch(exception) {
                  newCustomer.return(exception);
                }
              }
            });
          }
        });
        return newCustomer.wait();
      } else {
        throw new Meteor.Error('username-exists', 'Sorry, that username is already active!');
      }
    } else {
      throw new Meteor.Erro('email-exists', 'Sorry, that email is already active!')
    }
  },
})

〜/服务器/ stripe.js

const secret = Meteor.settings.private.stripe.testSecretKey;
const Stripe = StripeAPI(secret);

Meteor.methods({
  stripeCreateCustomer: function(token, email) {
    check(token, String);
    check(email, String);

    const stripeCustomer = new Future();

    Stripe.customers.create({
      source: token,
      email: email
    }, function(error, customer) {
      if(error){
        stripeCustomer.return(error);
      } else {
        stripeCustomer.return(customer);
      }
    });

    return stripeCustomer.wait();
  },

  stripeCreateSubscription: function(customer, plan) {
    check(customer, String);
    check(plan, String);

    const stripeSubscription = new Future();

    Stripe.customers.createSubscription(customer, {
      plan: plan
    }, function(error, subscription) {
      if(error) {
        stripeSubscription.return(error);
      } else {
        stripeSubscription.return(subscription);
      }
    });

    return stripeSubscription.wait();
  }
})

  "dependencies": {
    "meteor-node-stubs": "~0.2.0",
    "react": "^15.0.2",
    "react-addons-css-transition-group": "^15.0.2",
    "react-dom": "^15.0.2",
    "react-mounter": "^1.2.0"
  },



accounts-base
accounts-password
session
check
random

kadira:flow-router
ultimatejs:tracker-react
meteortoys:allthings

fourseven:scss
fortawesome:fontawesome
themeteorchef:bert
themeteorchef:jquery-validation
momentjs:moment

mrgalaxy:stripe

感谢阅读,我希望它不会感到痛苦。

1 个答案:

答案 0 :(得分:0)

你导入了mrgalaxy:stripe吗? 它会像

import { StripeAPI } from 'meteor/mrgalaxy:stripe'

我想。

anw,你可以通过npm安装: npm install stripe

import Stripe from 'stripe'

然后

Stripe('your_secret_key')