clover.pl 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. #!/usr/bin/env starman
  2. package Clover::Proxy;
  3. use strict;
  4. use warnings;
  5. use HTTP::Tiny;
  6. use HTTP::Body;
  7. use URL::Encode;
  8. use Data::Dumper;
  9. =head1 Clover::Proxy
  10. Application which proxies requests to the clover API, handling the oauth parts for you
  11. =cut
  12. # Clover generally expects that you have an HTTP endpoint on your end that behaves a particular way.
  13. # Example: $my_site/clover?code=AUTH_CODE&merchant_id=MERCH_ID
  14. # It is then expected to POST to the oauth endpoint.
  15. # Ye olde PSGI boilerstrap
  16. our $CHUNK_SIZE = 1024000;
  17. our %content_types = (
  18. text => "text/plain",
  19. html => "text/html",
  20. json => "application/json",
  21. blob => "application/octet-stream",
  22. xml => "text/xml",
  23. xsl => "text/xsl",
  24. css => "text/css",
  25. rss => "application/rss+xml",
  26. email => "multipart/related",
  27. );
  28. our %byct = reverse %content_types;
  29. my $ct = 'Content-type';
  30. my $cur_query = {};
  31. my %routes = (
  32. '/oauth' => \&oauth,
  33. );
  34. my $clover_uri = "https://apisandbox.dev.clover.com";
  35. # On startup, let's oauth.
  36. sub new {
  37. my $self = {};
  38. $self->{agent} = HTTP::Tiny->new();
  39. # TODO Grab our 2 secrets from config
  40. bless($self, __PACKAGE__);
  41. return $self;
  42. }
  43. sub agent { $_[0]->{agent} }
  44. sub oauth {
  45. my ($self, $data) = @_;
  46. my $oauth_endpoint = 'oauth/v2/authorize';
  47. my $token_endpoint = 'oauth/v2/token';
  48. my $client_id = 'W5QCN9ZCQX4WT';
  49. my $client_secret = 'df5ac68e-2cc5-8e4d-0788-928ea78615c1';
  50. # This is going to be provided to us by the HTTP endpoint listed above
  51. my $auth_code = $data->{code};
  52. return badrequest() unless $auth_code;
  53. my $oauth_data = {
  54. client_id => $client_id,
  55. client_secret => $client_secret,
  56. authorization_code => $auth_code,
  57. };
  58. return [200, ['Content-type', 'text/plain'], [Dumper($oauth_data)]];
  59. # Should give us an access_token and refresh_token
  60. die Dumper($self->agent->request('POST', qq{$clover_uri/$oauth_endpoint}, $oauth_data ));
  61. # TODO store this data in ye olde DB, and do cache invalidation etc
  62. }
  63. sub badrequest {
  64. return _generic(400, "Bad Request");
  65. }
  66. sub error {
  67. return _generic(500, "Internal Server Error");
  68. }
  69. sub unavailable {
  70. return _generic(503, "Service Unavailable");
  71. }
  72. sub toolong {
  73. return _generic(419, "Excessive URI Length");
  74. }
  75. sub _generic {
  76. my ($code, $msg) = @_;
  77. return [$code, [$ct => $content_types{html}], [$msg]];
  78. }
  79. sub proxy {
  80. my ($self, $method, $route, $data) = @_;
  81. die Dumper($self->agent->request($method, qq{$clover_uri/$route}, $data));
  82. }
  83. sub _parse_request {
  84. my ($env) = @_;
  85. my $query = URL::Encode::url_params_mixed( $env->{QUERY_STRING} ) if $env->{QUERY_STRING};
  86. #Actually parse the POSTDATA and dump it into the QUERY object if this is a POST
  87. if ( $env->{REQUEST_METHOD} eq 'POST' ) {
  88. my $body = HTTP::Body->new( $env->{CONTENT_TYPE}, $env->{CONTENT_LENGTH} );
  89. while ( $env->{'psgi.input'}->read( my $buf, $CHUNK_SIZE ) ) {
  90. $body->add($buf);
  91. }
  92. @$query{ keys( %{ $body->param } ) } = values( %{ $body->param } );
  93. @$query{ keys( %{ $body->upload } ) } = values( %{ $body->upload } );
  94. }
  95. return $query;
  96. }
  97. # Just dispatch it
  98. sub app {
  99. my ($self, $env) = @_;
  100. return toolong() if length( $env->{REQUEST_URI} ) > 2048;
  101. my $route = $env->{PATH_INFO};
  102. my $method = $env->{REQUEST_METHOD};
  103. my $data = _parse_request($env);
  104. return $routes{$route}->($self, $data) if $routes{$route};
  105. # Otherwise just proxy stuffs if we have a token
  106. return unavailable() unless $self->_has_valid_token();
  107. return $self->proxy($route, $method, $data);
  108. }
  109. my %options;
  110. # Don't ever put anything past $app
  111. our $app = sub {
  112. my $self = __PACKAGE__->new(%options);
  113. return eval { $self->app(@_) } || do {
  114. my $env = shift;
  115. $env->{'psgi.errors'}->print($@);
  116. # Redact the stack trace past line 1, it usually has things which should not be shown
  117. $self->{cur_query}->{message} = $@;
  118. $self->{cur_query}->{message} =~ s/\n.*//g if $self->{cur_query}->{message};
  119. return error($self->{cur_query});
  120. };
  121. };