Giter VIP home page Giter VIP logo

coinbase.commerce's Introduction

Build status Nuget Users

Coinbase.Commerce for .NET and C#

Project Description

💰 A C# API library and HTTP client implementation for the Coinbase Commerce API.

📢 HEY! Be sure to checkout these other Coinbase API integrations:

Minimum Requirements

  • .NET Standard 2.0 or later
  • .NET Framework 4.6.1 or later
  • TLS 1.2 or later

Note: If you are using .NET Framework 4.6.1 you will need to ensure your application is using TLS 1.2 or later. This can be configured via the registry (link 1, link 2) or configured at application startup by setting the following value in ServicePointManager:

ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;

Crypto Tip Jar

Download & Install

Nuget Package Coinbase.Commerce

Install-Package Coinbase.Commerce

Usage

Getting Started

You'll need to create Coinbase Commerce account. You can sign up here!

Receiving A Simple Crypto Payment

Suppose you want to charge a customer 1.00 USD for a candy bar and you'd like to receive payment in cryptocurrency like Bitcoin, Ethereum or Litecoin. The following C# creates a checkout page hosted at Coinbase Commerce:

var commerceApi = new CommerceApi(apiKey);

// Create a unique identifier to associate
// the customer in your system with the
// crypto payment they are about to make.
// Normally, this is a unique ID for your
// customer inside your database.
var customerId = Guid.NewGuid();

var charge = new CreateCharge
   {
      Name = "Candy Bar",
      Description = "Sweet Tasting Chocolate",
      PricingType = PricingType.FixedPrice,
      LocalPrice = new Money {Amount = 1.00m, Currency = "USD"},
      Metadata = // Here we associate the customer ID in our DB with the charge.
         {       // You can put any custom info here but keep it minimal.
            {"customerId", customerId}
         },
   };

var response = await commerceApi.CreateChargeAsync(charge);

// Check for any errors
if( response.HasError() )
{
   // Coinbase says something is wrong. Log the error 
   // and report back to the user an error has occurred.
   Console.WriteLine(response.Error.Message);
   Server.Render("Error creating checkout page.", 500);
   return;
}

// else, send the user to the hosted checkout page at Coinbase.
Server.Redirect(response.Data.HostedUrl);

When the customer is redirected to the HostedUrl checkout page on Coinbase the customer can pick their preference of cryptocurrency to pay with as shown below:

It's important to keep in mind that the customer has 15 minutes to complete the payment; otherwise the payment will fail.

Look Ma! No Redirects!

It's totally possible to perform a checkout without any redirects. "Whaaaat?!" I hear you say... That's right, you just need to roll your own custom UI.

In the previous example, if the charge creation was successful, you'll get back a Charge object in response.Data that looks like the object shown below:

{
  "data": {
    "code": "SOMECODE",
    "name": "Candy Bar",
    "description": "Sweet Tasting Chocolate",
    "logo_url": null,
    "hosted_url": "https://commerce.coinbase.com/charges/SOMECODE",
    "created_at": "2018-04-04T19:45:34+00:00",
    "expires_at": "2018-04-04T20:00:34+00:00",
    "confirmed_at": "0001-01-01T00:00:00+00:00",
    "checkout": null,
    "timeline": [
      {
        "time": "2018-04-04T19:45:34+00:00",
        "status": "NEW",
        "context": null
      }
    ],
    "metadata": {
      "customerId": "30025397-adff-4d80-8e9f-c231b582be85"
    },
    "pricing_type": "fixed_price",
    "pricing": {...},
    "payments": [],
    "addresses": {
      "ethereum": "0xeb294D2BCb1Cf25cBEBd0bF55160aA655F82D8c0",
      "bitcoin": "1KgpR5rQmFpfvxQKdPsL9jU8FPf35xmjvn",
      "bitcoincash": "DGVC2drEMt41sEzEHSsiE3VTrgsQxGn5qe",
      "litecoin": "LXWELKEw124ryu3hbwzBJPUy81odeLthkv"
    }
  },
  "error": null,
  "warnings": null
}

Wonderful! Notice the data.addresses dictionary of bitcoin, ethereum and litecoin addresses above. So, instead of sending a redirect like the last example, you can use these crypto addresses to generate QR codes in your custom UI. The same timelimit and rules apply, the customer has 15 minutes to complete the payment.

Webhooks: 'Don't call us, we'll call you...'

If you want to receive notifications on your server when Charges are created, confirmed (aka completed), or failed you'll need to listen for events from Coinbase on your server. You can do this using Webhooks.

Go to the Settings tab in your Coinbase Commerce account and create a Webhook Subscription as shown below:

💡 Protip: Consider using smee.io or ngrok to help you debug webhook callbacks while in development.

When a charge:created, charge:confirmed, or charge:failed event occurs, Coinbase will POST JSON to your /callmebackhere endpoint. The HTTP POST looks something like:

POST /callmebackhere HTTP/1.1
User-Agent: weipay-webhooks
Content-Type: application/json
X-Cc-Webhook-Signature: cb22789c9d5c344a10e0474f134db39e25eb3bbf5a1b1a5e89b507f15ea9519c
Accept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3
Accept: */*
Connection: close
Content-Length: 1122

{"attempt_number":1,"event":{"api_version":"2018-03-22","created_at":"2018-04-04T23:49:00Z","data":{"code":"8EKMDPVQ","name":"Candy Bar",...."description":"Sweet Tasting Chocolate","pricing_type":"fixed_price"},"id":"f6972c57-c100-4e64-b47c-193adecfadc6","type":"charge:created"}}

The two important pieces of information you need to extract from this HTTP POST callback are:

  • The X-Cc-Webhook-Signature header value.
  • The raw HTTP body JSON payload.

The value of the X-Cc-Webhook-Signature header is a HMACSHA256 signature of the raw HTTP JSON computed using your Webhook Shared Secret as a key.

The WebhookHelper static class included with this library does all the heavy lifting for you. All you need to do is call Webhookhelper.IsValid() supplying your Webhook Shared Secret key, the X-Cc-Webhook-Signature header value in the HTTP POST above, and finally the raw JSON body in the HTTP POST above.

The following C# code shows how to use the WebhookHelper to validate callbacks from Coinbase:

if( WebhookHelper.IsValid("sharedSecretKey", webhookHeaderValue, Request.Body.Json) ){
   // The request is legit and an authentic message from Coinbase.
   // It's safe to deserialize the JSON body. 
   var webhook = JsonConvert.DeserializeObject<Webhook>(Request.Body.Json);

   var chargeInfo = webhook.Event.DataAs<Charge>();

   // Remember that customer ID we created back in the first example?
   // Here's were we can extract that information from the callback.
   var customerId = chargeInfo.Metadata["customerId"].ToObject<string>();

   if (webhook.Event.IsChargeFailed)
   {
      // The payment failed. Log something.
      Database.MarkPaymentFailed(customerId);
   }
   else if (webhook.Event.IsChargeCreated)
   {
      // The charge was created just now.
      // Do something with the newly created
      // event.
      Database.MarkPaymentPending(customerId)
   } 
   else if( webhook.Event.IsChargeConfirmed )
   {
      // The payment was confirmed.
      // Fulfill the order!
      Database.ShipCandyBar(customerId)
   }

   return Response.Ok();
}
else {
   // Some hackery going on. The Webhook message validation failed.
   // Someone is trying to spoof payment events!
   // Log the requesting IP address and HTTP body. 
}

Sample callback processing code for ASP.NET can be found here. Easy peasy! Happy crypto shopping! 🎉

Building

  • Download the source code.
  • Run build.cmd.

Upon successful build, the results will be in the \__compile directory. If you want to build NuGet packages, run build.cmd pack and the NuGet packages will be in __package.


Note: This application/third-party library is not directly supported by Coinbase Inc. Coinbase Inc. makes no claims about this application/third-party library. This application/third-party library is not endorsed or certified by Coinbase Inc.

coinbase.commerce's People

Contributors

bchavez avatar kirillkaverin avatar sthewissen avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

coinbase.commerce's Issues

Callback parameters

Hello, I'm doing the payment successfully, but I have not found which parameter to return in the callback address.
regards respects.

Webhook subscriptions : https://mysite.com/home/callback
public ActionResult callback(string ????)
{
// or Request.Form.Get("????");
try
{
var webhook = JsonConvert.DeserializeObject(????);
..............

Unable to use Nuget package

Hi @bchavez
Thanks for putting this up on GitHub.
I was trying to use your package in my WebAPI project but I am unable to get pass these errors. For some reason, the only namespace I see under using Coinbase is Coinbase.Models. I see your package under my References section. Not sure what I am missing here.. See image below.

image

Is this out of sync with the latest api?

The front end seems to work just fine but the back end hooks don't seem to be working.

Issue 1) the test posts from coinbase seem to now include pricing: nil which blows up. I managed to fix this by writing in some logic to remove it from the json if it shows up.

  1. The create seems to be working on but the confirm seems to always come back with invalid shared secret.

  2. possible side note, I'm working outside of MVC and despite it posting the test posts to my end point seem always seem to come back with a message that says "Failed to establish a connection to the remote server". I am returning 200 but it's not caring.

Appreciate any insight you can offer.


public HttpResponseMessage CallbackFunction ()
    {
        try
        {
            string SHARED_SECRET = "<Secret>";

            var requestSignature = Request.Headers[HeaderNames.WebhookSignature];
            //owner_functions.CreateGeneralLog("CoinBase", requestSignature);
            Request.InputStream.Seek(0, SeekOrigin.Begin);
            var json = new StreamReader(Request.InputStream).ReadToEnd();

            //clear up bad information if it's passed in
            if (json.Contains("\"pricing\":\"nil\",")) { json = json.Replace("\"pricing\":\"nil\",", ""); }

            owner_functions.CreateGeneralLog("CoinBase", json);

            if (!WebhookHelper.IsValid(SHARED_SECRET, requestSignature, json))
            {
                //fail
                owner_functions.CreateGeneralLog("CoinBase", "INVALID SHARED SECRET");
                return new HttpResponseMessage(HttpStatusCode.Unauthorized);

            }

            var webhook = JsonConvert.DeserializeObject<Webhook>(json);
            
            
            var chargeInfo = webhook.Event.DataAs<Charge>();
            try
            {
                var customerId = chargeInfo.Metadata["customerId"].ToObject<string>();
            }
            catch { owner_functions.CreateGeneralLog("CoinBase Error", "No Customer ID Found"); }

            var charge = webhook.Event.DataAs<Charge>();

            if (webhook.Event.IsChargeCreated)
            {
                    // The charge was created just now.
                    // Do something with the newly created
                    // event.
                    owner_functions.CreateGeneralLog("CoinBase", "Charge Created");
                    return new HttpResponseMessage(HttpStatusCode.OK);

            }

            else if (webhook.Event.IsChargeConfirmed)
            {
                    // The payment was confirmed
                    owner_functions.CreateGeneralLog("CoinBase", "Charge Confirmed");
                    return new HttpResponseMessage(HttpStatusCode.OK);

            }

            else if (webhook.Event.IsChargeFailed)
            {
                    // The payment failed. Log something.
                    owner_functions.CreateGeneralLog("CoinBase", "Charge Failed");
                    return new HttpResponseMessage(HttpStatusCode.OK);
            }            

            return new HttpResponseMessage(HttpStatusCode.OK);
        }
        catch (Exception ex) {
            owner_functions.CreateGeneralLog("CoinBase Error", ex.ToString());
            return new HttpResponseMessage(HttpStatusCode.BadRequest);
        }
    }

Null from api request when charge is pending

Version Information

Software Version(s)
NuGet Package this one
.NET Core? 2.1.802
.NET Full Framework? 4.6
Windows OS? windows 10
Linux OS? N/A
Visual Studio? 19

What is the expected behavior?

Just get successfully get the charge from api request

What is the actual behavior?

Request works until the charge status is set to pending

Any possible solutions?

thats why im here

How do you reproduce the issue?

Just make a charge send the money and try and request the api when its pending and you get a
null error json failed to deserialize

Do you have a unit test that can demonstrate the bug?

No

Can you identify the location in the source code where the problem exists?

responsee = await commerceApi.GetChargeAsync(response.Data.Code);

IP ranges

Hi there

We prepared everything, but we need to have the IP ranges used by coinbase to configure our security settings on the server.

Can you provide a specific IP range or connection IP which is used by this service?
Coinbase Commerce Support couldnt send any so far.

Thanks
Michael

Cannot run tests using 'build.cmd test'

Version Information

Software Version(s)
.NET Core? 2.1.801, 3.1.100
.NET Full Framework? 4.8
Windows OS? Yes. Win 10 Pro 1903 (10.0.18362)

What is the expected behavior?

Build succeeded

What is the actual behavior?

Build failed

Other notes on how to reproduce the issue?

Error:
System.Exception: Test failed on "test" --configuration Release --test-adapter-path:. --logger:"nunit;LogFilePath=Y:\work\Coinbase.Commerce\__test\results.xml"
   в [email protected](String message) в D:\code\fake\src\app\FakeLib\DotNetCLIHelper.fs:line 298
   в Fake.DotNetCli.Test(FSharpFunc`2 setTestParams) в D:\code\fake\src\app\FakeLib\DotNetCLIHelper.fs:line 298
   в [email protected](Unit _arg4) в Y:\work\Coinbase.Commerce\Source\Builder\build.fsx:line 134
   в Fake.TargetHelper.runSingleTarget(TargetTemplate`1 target) в D:\code\fake\src\app\FakeLib\TargetHelper.fs:line 626

---------------------------------------------------------------------
Build Time Report
---------------------------------------------------------------------
Target      Duration
------      --------
Clean       00:00:00.0314926
restore     00:00:02.2319258
BuildInfo   00:00:00.0085929
dnx         00:00:04.6698891
test        Failure
Total:      00:00:11.7333330
---------------------------------------------------------------------
Status:     Failure
---------------------------------------------------------------------
---------------------------------------------------------------------
  1) System.Exception: Test failed on "test" --configuration Release --test-adapter-path:. --logger:"nunit;LogFilePath=Y:\work\Coinbase.Commerce\__test\results.xml"
   в [email protected](String message) в D:\code\fake\src\app\FakeLib\DotNetCLIHelper.fs:line 298
   в Fake.DotNetCli.Test(FSharpFunc`2 setTestParams) в D:\code\fake\src\app\FakeLib\DotNetCLIHelper.fs:line 298
   в [email protected](Unit _arg4) в Y:\work\Coinbase.Commerce\Source\Builder\build.fsx:line 134
   в Fake.TargetHelper.runSingleTarget(TargetTemplate`1 target) в D:\code\fake\src\app\FakeLib\TargetHelper.fs:line 626
---------------------------------------------------------------------

Any possible solutions?

Update NUnit3TestAdapter package to the latest version (3.15.1)

Paging @bchavez :)

Hi @bchavez I hope you are OK with using your issues board to page you. I am not sure of any other way to contact you. I wanted to let you know that Coinbase is actively engaging users on their new forum board and I brought up the issue in which we were both participants here:

coinbase/coinbase-ios-sdk#54

The new issue is here:
https://forums.coinbasecloud.dev/t/any-web2-support/745

I thought you would find that interesting and/or might want to participate in the discussion there.

Hope all is well for you out there! 👍

(Oh yeah, please close this thread upon receipt!)

Get currency paid with

How do i get the currency the user paid with, either BTC or ETH and how much is the coin amount from webhook ?
Something like this.
chargeInfo.Pricing["Amount"].Amount

charge

CreateInvoice returns Uri in data.hostedurl

Hello thanks for the awesome library!
Is there any reason

CreateInvoiceAsync returns a URI in data.HostedUrl

and

CreateChargeAsync returns a string in the same field?

also, just to confirm is this the right way of passing multiple metadata:

Metadata =
{
{"userID", userID},
{"product", product},
{"extraID", extraID}
}
?

also, should

var commerceApi = new CommerceApi(APIKey);

.. be created for every transaction or is it safe to just create it once in the application?

and for bonus points, how do i attach metadata to an invoice? I couldn't see how to marry up the charge that is generated by the invoice with any meta data about the order.

sorry for the multiple questions!

Question: Charge vs Checkout Charge

First off, thank you so much for putting this library together! Especially the Webhook integration. It's going to save me a lot of work. 👍

I did have a question that perhaps you can help answer for me. In my Coinbase Commerce account, I have created a Checkout. Now, in the API, it says Checkouts can have many charges and each charge is automatically generated on a per customer basis.

However, when creating a charge, I do not see where to assign the checkout, neither in this API nor on Coinbase Commerce itself. So, I'm now officially confused. 😅

My understanding is you create a checkout and when you visit that checkout it creates a charge. You can also create a charge directly. However, it doesn't seem you can create a charge from a checkout (even though it seems this happens when you visit a created checkout from the Commerce settings).

I hope this makes sense. It would be really helpful to get this figured out.

Otherwise, great work out there. The documentation, too!

The application completed without reading the entire request body.

Request.Body.Seek(0, SeekOrigin.Begin); using (var reader = new StreamReader(Request.Body)) { var json =await reader.ReadToEndAsync(); }

when testing my webhook, it throws 500 internal server error. and then in my server i see this error "The application completed without reading the entire request body."

Error converting value {null} to type 'System.Int64'. Path 'payments[0].block.height'

Version Information

Software Version(s)
NuGet Package 1.10.0

What is the expected behavior?

No error while convert webhook body to the object.

What is the actual behavior?

Error converting value {null} to type 'System.Int64'. Path 'payments[0].block.height'

Please provide a unit test that demonstrates the bug.

Webhook example to reproduce:

{
  "attempt_number": 1,
  "event": {
    "api_version": "2018-03-22",
    "created_at": "2019-12-19T16:18:40Z",
    "data": {
      "id": "foo",
      "code": "foo",
      "name": "foo",
      "pricing": {
        "usdc": {
          "amount": "1275.000000",
          "currency": "USDC"
        },
        "local": {
          "amount": "1275.00",
          "currency": "USD"
        },
        "bitcoin": {
          "amount": "0.17824340",
          "currency": "BTC"
        },
        "ethereum": {
          "amount": "9.982384000",
          "currency": "ETH"
        },
        "litecoin": {
          "amount": "31.90291505",
          "currency": "LTC"
        },
        "bitcoincash": {
          "amount": "6.76410515",
          "currency": "BCH"
        }
      },
      "logo_url": "foo",
      "metadata": {
        "DepositId": "111"
      },
      "payments": [
        {
          "block": {
            "hash": null,
            "height": null,
            "confirmations": 0,
            "confirmations_required": 1
          },
          "value": {
            "local": {
              "amount": "1274.67",
              "currency": "USD"
            },
            "crypto": {
              "amount": "0.17819718",
              "currency": "BTC"
            }
          },
          "status": "PENDING",
          "network": "bitcoin",
          "detected_at": "2019-12-19T16:18:40Z",
          "transaction_id": "foo"
        }
      ],
      "resource": "charge",
      "timeline": [
        {
          "time": "2019-12-19T16:18:11Z",
          "status": "NEW"
        },
        {
          "time": "2019-12-19T16:18:40Z",
          "status": "PENDING",
          "payment": {
            "network": "bitcoin",
            "transaction_id": "foo"
          }
        }
      ],
      "addresses": {
        "usdc": "foo",
        "bitcoin": "foo",
        "ethereum": "foo",
        "litecoin": "foo",
        "bitcoincash": "foo"
      },
      "created_at": "2019-12-19T16:18:11Z",
      "expires_at": "2019-12-19T17:18:11Z",
      "hosted_url": "https:\/\/commerce.coinbase.com\/charges\/foo",
      "description": "foo",
      "pricing_type": "fixed_price"
    },
    "id": "foo",
    "resource": "event",
    "type": "charge:pending"
  },
  "id": "foo",
  "scheduled_for": "2019-12-19T16:18:40Z"
}

Other notes on how to reproduce the issue?

Coinbase sends the 'height' null value only when webhook type is 'charge:pending'

Any possible solutions?

Change in Block class from
public long Height { get; set; }
to
public long? Height { get; set; }

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.